diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj
index 165b35460..c50137e25 100644
--- a/src/ClassiCube.vcxproj
+++ b/src/ClassiCube.vcxproj
@@ -318,6 +318,11 @@
+
+
+
+
+
diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters
index e8831ae8a..50399b1a5 100644
--- a/src/ClassiCube.vcxproj.filters
+++ b/src/ClassiCube.vcxproj.filters
@@ -566,5 +566,20 @@
Source Files\Network
+
+ Source Files\Platform
+
+
+ Source Files\Platform
+
+
+ Source Files\Platform
+
+
+ Source Files\Platform
+
+
+ Source Files\Platform
+
\ No newline at end of file
diff --git a/src/Core.h b/src/Core.h
index 1232e5e70..bb4c3ad97 100644
--- a/src/Core.h
+++ b/src/Core.h
@@ -154,7 +154,6 @@ Thus it is **NOT SAFE** to allocate a string on the stack. */
#define CC_BUILD_WIN
#define CC_BUILD_D3D9
#define CC_BUILD_WINGUI
-#define CC_BUILD_WGL
#define CC_BUILD_WININET
#define CC_BUILD_WINMM
#elif defined __ANDROID__
diff --git a/src/Window.c b/src/Window.c
index 4df1b79bb..995451751 100644
--- a/src/Window.c
+++ b/src/Window.c
@@ -1,1985 +1,13 @@
-#include "String.h"
-#include "Platform.h"
-#include "Funcs.h"
-#include "ExtMath.h"
-#include "Bitmap.h"
-#include "Options.h"
-#include "Errors.h"
-
-/*########################################################################################################################*
-*-------------------------------------------------------SDL window--------------------------------------------------------*
-*#########################################################################################################################*/
-#if defined CC_BUILD_SDL
-#include "_WindowBase.h"
-#include "Graphics.h"
-#include
-static SDL_Window* win_handle;
-
-static void RefreshWindowBounds(void) {
- SDL_GetWindowSize(win_handle, &WindowInfo.Width, &WindowInfo.Height);
-}
-
-static void Window_SDLFail(const char* place) {
- char strBuffer[256];
- cc_string str;
- String_InitArray_NT(str, strBuffer);
-
- String_Format2(&str, "Error when %c: %c", place, SDL_GetError());
- str.buffer[str.length] = '\0';
- Logger_Abort(str.buffer);
-}
-
-void Window_Init(void) {
- SDL_DisplayMode mode = { 0 };
- SDL_Init(SDL_INIT_VIDEO);
- SDL_GetDesktopDisplayMode(0, &mode);
-
- DisplayInfo.Width = mode.w;
- DisplayInfo.Height = mode.h;
- DisplayInfo.Depth = SDL_BITSPERPIXEL(mode.format);
- DisplayInfo.ScaleX = 1;
- DisplayInfo.ScaleY = 1;
-}
-
-void Window_Create(int width, int height) {
- int x = Display_CentreX(width);
- int y = Display_CentreY(height);
-
- /* TODO: Don't set this flag for launcher window */
- win_handle = SDL_CreateWindow(NULL, x, y, width, height, SDL_WINDOW_OPENGL);
- if (!win_handle) Window_SDLFail("creating window");
-
- RefreshWindowBounds();
- WindowInfo.Exists = true;
- WindowInfo.Handle = win_handle;
-}
-
-void Window_SetTitle(const cc_string* title) {
- char str[NATIVE_STR_LEN];
- Platform_EncodeUtf8(str, title);
- SDL_SetWindowTitle(win_handle, str);
-}
-
-void Clipboard_GetText(cc_string* value) {
- char* ptr = SDL_GetClipboardText();
- if (!ptr) return;
-
- int len = String_Length(ptr);
- String_AppendUtf8(value, ptr, len);
- SDL_free(ptr);
-}
-
-void Clipboard_SetText(const cc_string* value) {
- char str[NATIVE_STR_LEN];
- Platform_EncodeUtf8(str, value);
- SDL_SetClipboardText(str);
-}
-
-void Window_Show(void) { SDL_ShowWindow(win_handle); }
-
-int Window_GetWindowState(void) {
- Uint32 flags = SDL_GetWindowFlags(win_handle);
-
- if (flags & SDL_WINDOW_MINIMIZED) return WINDOW_STATE_MINIMISED;
- if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) return WINDOW_STATE_FULLSCREEN;
- return WINDOW_STATE_NORMAL;
-}
-
-cc_result Window_EnterFullscreen(void) {
- return SDL_SetWindowFullscreen(win_handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
-}
-cc_result Window_ExitFullscreen(void) { SDL_RestoreWindow(win_handle); return 0; }
-
-void Window_SetSize(int width, int height) {
- SDL_SetWindowSize(win_handle, width, height);
-}
-
-void Window_Close(void) {
- SDL_Event e;
- e.type = SDL_QUIT;
- SDL_PushEvent(&e);
-}
-
-static int MapNativeKey(SDL_Keycode k) {
- if (k >= SDLK_0 && k <= SDLK_9) { return '0' + (k - SDLK_0); }
- if (k >= SDLK_a && k <= SDLK_z) { return 'A' + (k - SDLK_a); }
- if (k >= SDLK_F1 && k <= SDLK_F12) { return KEY_F1 + (k - SDLK_F1); }
- if (k >= SDLK_F13 && k <= SDLK_F24) { return KEY_F13 + (k - SDLK_F13); }
- /* SDLK_KP_0 isn't before SDLK_KP_1 */
- if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return KEY_KP1 + (k - SDLK_KP_1); }
-
- switch (k) {
- case SDLK_RETURN: return KEY_ENTER;
- case SDLK_ESCAPE: return KEY_ESCAPE;
- case SDLK_BACKSPACE: return KEY_BACKSPACE;
- case SDLK_TAB: return KEY_TAB;
- case SDLK_SPACE: return KEY_SPACE;
- case SDLK_QUOTE: return KEY_QUOTE;
- case SDLK_EQUALS: return KEY_EQUALS;
- case SDLK_COMMA: return KEY_COMMA;
- case SDLK_MINUS: return KEY_MINUS;
- case SDLK_PERIOD: return KEY_PERIOD;
- case SDLK_SLASH: return KEY_SLASH;
- case SDLK_SEMICOLON: return KEY_SEMICOLON;
- case SDLK_LEFTBRACKET: return KEY_LBRACKET;
- case SDLK_BACKSLASH: return KEY_BACKSLASH;
- case SDLK_RIGHTBRACKET: return KEY_RBRACKET;
- case SDLK_BACKQUOTE: return KEY_TILDE;
- case SDLK_CAPSLOCK: return KEY_CAPSLOCK;
- case SDLK_PRINTSCREEN: return KEY_PRINTSCREEN;
- case SDLK_SCROLLLOCK: return KEY_SCROLLLOCK;
- case SDLK_PAUSE: return KEY_PAUSE;
- case SDLK_INSERT: return KEY_INSERT;
- case SDLK_HOME: return KEY_HOME;
- case SDLK_PAGEUP: return KEY_PAGEUP;
- case SDLK_DELETE: return KEY_DELETE;
- case SDLK_END: return KEY_END;
- case SDLK_PAGEDOWN: return KEY_PAGEDOWN;
- case SDLK_RIGHT: return KEY_RIGHT;
- case SDLK_LEFT: return KEY_LEFT;
- case SDLK_DOWN: return KEY_DOWN;
- case SDLK_UP: return KEY_UP;
-
- case SDLK_NUMLOCKCLEAR: return KEY_NUMLOCK;
- case SDLK_KP_DIVIDE: return KEY_KP_DIVIDE;
- case SDLK_KP_MULTIPLY: return KEY_KP_MULTIPLY;
- case SDLK_KP_MINUS: return KEY_KP_MINUS;
- case SDLK_KP_PLUS: return KEY_KP_PLUS;
- case SDLK_KP_ENTER: return KEY_KP_ENTER;
- case SDLK_KP_0: return KEY_KP0;
- case SDLK_KP_PERIOD: return KEY_KP_DECIMAL;
-
- case SDLK_LCTRL: return KEY_LCTRL;
- case SDLK_LSHIFT: return KEY_LSHIFT;
- case SDLK_LALT: return KEY_LALT;
- case SDLK_LGUI: return KEY_LWIN;
- case SDLK_RCTRL: return KEY_RCTRL;
- case SDLK_RSHIFT: return KEY_RSHIFT;
- case SDLK_RALT: return KEY_RALT;
- case SDLK_RGUI: return KEY_RWIN;
- }
- return KEY_NONE;
-}
-
-static void OnKeyEvent(const SDL_Event* e) {
- cc_bool pressed = e->key.state == SDL_PRESSED;
- int key = MapNativeKey(e->key.keysym.sym);
- if (key) Input_Set(key, pressed);
-}
-
-static void OnMouseEvent(const SDL_Event* e) {
- cc_bool pressed = e->button.state == SDL_PRESSED;
- int btn;
- switch (e->button.button) {
- case SDL_BUTTON_LEFT: btn = KEY_LMOUSE; break;
- case SDL_BUTTON_MIDDLE: btn = KEY_MMOUSE; break;
- case SDL_BUTTON_RIGHT: btn = KEY_RMOUSE; break;
- case SDL_BUTTON_X1: btn = KEY_XBUTTON1; break;
- case SDL_BUTTON_X2: btn = KEY_XBUTTON2; break;
- default: return;
- }
- Input_Set(btn, pressed);
-}
-
-static void OnTextEvent(const SDL_Event* e) {
- char buffer[SDL_TEXTINPUTEVENT_TEXT_SIZE];
- cc_string str;
- int i, len;
-
- String_InitArray(str, buffer);
- len = String_CalcLen(e->text.text, SDL_TEXTINPUTEVENT_TEXT_SIZE);
- String_AppendUtf8(&str, e->text.text, len);
-
- for (i = 0; i < str.length; i++) {
- Event_RaiseInt(&InputEvents.Press, str.buffer[i]);
- }
-}
-
-static void OnWindowEvent(const SDL_Event* e) {
- switch (e->window.event) {
- case SDL_WINDOWEVENT_EXPOSED:
- Event_RaiseVoid(&WindowEvents.Redraw);
- break;
- case SDL_WINDOWEVENT_SIZE_CHANGED:
- RefreshWindowBounds();
- Event_RaiseVoid(&WindowEvents.Resized);
- break;
- case SDL_WINDOWEVENT_MINIMIZED:
- case SDL_WINDOWEVENT_MAXIMIZED:
- case SDL_WINDOWEVENT_RESTORED:
- Event_RaiseVoid(&WindowEvents.StateChanged);
- break;
- case SDL_WINDOWEVENT_FOCUS_GAINED:
- WindowInfo.Focused = true;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
- break;
- case SDL_WINDOWEVENT_FOCUS_LOST:
- WindowInfo.Focused = false;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
- break;
- case SDL_WINDOWEVENT_CLOSE:
- Window_Close();
- break;
- }
-}
-
-void Window_ProcessEvents(void) {
- SDL_Event e;
- while (SDL_PollEvent(&e)) {
- switch (e.type) {
-
- case SDL_KEYDOWN:
- case SDL_KEYUP:
- OnKeyEvent(&e); break;
- case SDL_MOUSEBUTTONDOWN:
- case SDL_MOUSEBUTTONUP:
- OnMouseEvent(&e); break;
- case SDL_MOUSEWHEEL:
- Mouse_ScrollWheel(e.wheel.y);
- break;
- case SDL_MOUSEMOTION:
- Pointer_SetPosition(0, e.motion.x, e.motion.y);
- if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel);
- break;
- case SDL_TEXTINPUT:
- OnTextEvent(&e); break;
- case SDL_WINDOWEVENT:
- OnWindowEvent(&e); break;
-
- case SDL_QUIT:
- WindowInfo.Exists = false;
- Event_RaiseVoid(&WindowEvents.Closing);
- SDL_DestroyWindow(win_handle);
- break;
-
- case SDL_RENDER_DEVICE_RESET:
- Gfx_LoseContext("SDL device reset event");
- Gfx_RecreateContext();
- break;
- }
- }
-}
-
-static void Cursor_GetRawPos(int* x, int* y) {
- SDL_GetMouseState(x, y);
-}
-void Cursor_SetPosition(int x, int y) {
- SDL_WarpMouseInWindow(win_handle, x, y);
-}
-
-static void Cursor_DoSetVisible(cc_bool visible) {
- SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
-}
-
-static void ShowDialogCore(const char* title, const char* msg) {
- SDL_ShowSimpleMessageBox(0, title, msg, win_handle);
-}
-
-static SDL_Surface* surface;
-void Window_AllocFramebuffer(struct Bitmap* bmp) {
- surface = SDL_GetWindowSurface(win_handle);
- if (!surface) Window_SDLFail("getting window surface");
-
- if (SDL_MUSTLOCK(surface)) {
- int ret = SDL_LockSurface(surface);
- if (ret < 0) Window_SDLFail("locking window surface");
- }
- bmp->scan0 = surface->pixels;
-}
-
-void Window_DrawFramebuffer(Rect2D r) {
- SDL_Rect rect;
- rect.x = r.X; rect.w = r.Width;
- rect.y = r.Y; rect.h = r.Height;
- SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1);
-}
-
-void Window_FreeFramebuffer(struct Bitmap* bmp) {
- /* SDL docs explicitly say to NOT free the surface */
- /* https://wiki.libsdl.org/SDL_GetWindowSurface */
- /* TODO: Do we still need to unlock it though? */
-}
-
-void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { SDL_StartTextInput(); }
-void Window_SetKeyboardText(const cc_string* text) { }
-void Window_CloseKeyboard(void) { SDL_StopTextInput(); }
-
-void Window_EnableRawMouse(void) {
- RegrabMouse();
- SDL_SetRelativeMouseMode(true);
- Input_RawMode = true;
-}
-void Window_UpdateRawMouse(void) { CentreMousePosition(); }
-
-void Window_DisableRawMouse(void) {
- RegrabMouse();
- SDL_SetRelativeMouseMode(false);
- Input_RawMode = false;
-}
-
-
-/*########################################################################################################################*
-*------------------------------------------------------Win32 window-------------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_WINGUI
-#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 "_WindowBase.h"
-#include
-
-#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
-
-typedef BOOL (WINAPI *FUNC_RegisterRawInput)(PCRAWINPUTDEVICE devices, UINT numDevices, UINT size);
-static FUNC_RegisterRawInput _registerRawInput;
-typedef UINT (WINAPI *FUNC_GetRawInputData)(HRAWINPUT hRawInput, UINT cmd, void* data, UINT* size, UINT headerSize);
-static FUNC_GetRawInputData _getRawInputData;
-
-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 int windowX, windowY;
-static cc_bool is_ansiWindow;
-
-static const cc_uint8 key_map[14 * 16] = {
- 0, 0, 0, 0, 0, 0, 0, 0, KEY_BACKSPACE, KEY_TAB, 0, 0, 0, KEY_ENTER, 0, 0,
- 0, 0, 0, KEY_PAUSE, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, 0, 0, 0, 0,
- KEY_SPACE, KEY_PAGEUP, KEY_PAGEDOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, 0, KEY_PRINTSCREEN, 0, KEY_PRINTSCREEN, KEY_INSERT, KEY_DELETE, 0,
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
- 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
- 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', KEY_LWIN, KEY_RWIN, KEY_MENU, 0, 0,
- KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP_MULTIPLY, KEY_KP_PLUS, 0, KEY_KP_MINUS, KEY_KP_DECIMAL, KEY_KP_DIVIDE,
- KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16,
- KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, 0, 0, 0, 0, 0, 0, 0, 0,
- KEY_NUMLOCK, KEY_SCROLLLOCK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_SEMICOLON, KEY_EQUALS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH,
- KEY_TILDE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_LBRACKET, KEY_BACKSLASH, KEY_RBRACKET, KEY_QUOTE, 0,
-};
-static int MapNativeKey(WPARAM key, LPARAM meta) {
- LPARAM ext = meta & (1UL << 24);
- switch (key)
- {
- case VK_CONTROL:
- return ext ? KEY_RCTRL : KEY_LCTRL;
- case VK_MENU:
- return ext ? KEY_RALT : KEY_LALT;
- case VK_RETURN:
- return ext ? KEY_KP_ENTER : KEY_ENTER;
- default:
- return key < Array_Elems(key_map) ? key_map[key] : 0;
- }
-}
-
-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);
- WindowInfo.Width = Rect_Width(rect);
- WindowInfo.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 LRESULT CALLBACK Window_Procedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
- char keyChar;
- float wheelDelta;
-
- switch (message) {
- case WM_ACTIVATE:
- WindowInfo.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.Redraw);
- return 0;
-
- case WM_WINDOWPOSCHANGED:
- {
- WINDOWPOS* pos = (WINDOWPOS*)lParam;
- if (pos->hwnd != win_handle) break;
- cc_bool sized = pos->cx != win_totalWidth || pos->cy != win_totalHeight;
-
- 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 */
- if (Convert_TryCodepointToCP437((cc_unichar)wParam, &keyChar)) {
- Event_RaiseInt(&InputEvents.Press, keyChar);
- }
- break;
-
- case WM_MOUSEMOVE:
- /* Set before position change, in case mouse buttons changed when outside window */
- Input_SetNonRepeatable(KEY_LMOUSE, wParam & 0x01);
- Input_SetNonRepeatable(KEY_RMOUSE, wParam & 0x02);
- Input_SetNonRepeatable(KEY_MMOUSE, 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_ScrollWheel(wheelDelta);
- return 0;
-
- case WM_LBUTTONDOWN:
- Input_SetPressed(KEY_LMOUSE); break;
- case WM_MBUTTONDOWN:
- Input_SetPressed(KEY_MMOUSE); break;
- case WM_RBUTTONDOWN:
- Input_SetPressed(KEY_RMOUSE); break;
- case WM_XBUTTONDOWN:
- Input_SetPressed(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
- break;
-
- case WM_LBUTTONUP:
- Input_SetReleased(KEY_LMOUSE); break;
- case WM_MBUTTONUP:
- Input_SetReleased(KEY_MMOUSE); break;
- case WM_RBUTTONUP:
- Input_SetReleased(KEY_RMOUSE); break;
- case WM_XBUTTONUP:
- Input_SetReleased(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
- break;
-
- case WM_INPUT:
- {
- RAWINPUT raw;
- UINT ret, rawSize = sizeof(RAWINPUT);
- int dx, dy;
-
- 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) {
- dx = raw.data.mouse.lLastX;
- dy = raw.data.mouse.lLastY;
- } else if (raw.data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
- static int prevPosX, prevPosY;
- dx = raw.data.mouse.lLastX - prevPosX;
- dy = raw.data.mouse.lLastY - prevPosY;
-
- prevPosX = raw.data.mouse.lLastX;
- prevPosY = raw.data.mouse.lLastY;
- } 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(KEY_LSHIFT, lShiftDown);
- Input_Set(KEY_RSHIFT, rShiftDown);
- }
- } else {
- key = MapNativeKey(wParam, lParam);
- if (key) Input_Set(key, pressed);
- else Platform_Log1("Unknown key: %x", &wParam);
- }
- 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 (WindowInfo.Exists) DestroyWindow(win_handle);
- WindowInfo.Exists = false;
- break;
-
- case WM_DESTROY:
- WindowInfo.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_Init(void) {
- HDC 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);
-}
-
-static ATOM DoRegisterClass(void) {
- ATOM atom;
- WNDCLASSEXW wc = { 0 };
- wc.cbSize = sizeof(WNDCLASSEXW);
- wc.style = CS_OWNDC;
- 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, IDC_ARROW);
-
- if ((atom = RegisterClassExW(&wc))) return atom;
- /* Windows 9x does not support W API functions */
- return RegisterClassExA((const WNDCLASSEXA*)&wc);
-}
-
-static void DoCreateWindow(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, 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");
-}
-
-void Window_Create(int width, int height) {
- ATOM atom;
- win_instance = GetModuleHandleA(NULL);
- /* TODO: UngroupFromTaskbar(); */
- width = Display_ScaleX(width);
- height = Display_ScaleY(height);
-
- atom = DoRegisterClass();
- DoCreateWindow(atom, width, height);
- RefreshWindowBounds();
-
- win_DC = GetDC(win_handle);
- if (!win_DC) Logger_Abort2(GetLastError(), "Failed to get device context");
- WindowInfo.Exists = true;
- WindowInfo.Handle = win_handle;
-}
-
-void Window_SetTitle(const cc_string* title) {
- WCHAR str[NATIVE_STR_LEN];
- Platform_EncodeUtf16(str, title);
- if (SetWindowTextW(win_handle, str)) return;
-
- /* Windows 9x does not support W API functions */
- Platform_Utf16ToAnsi(str);
- SetWindowTextA(win_handle, (const char*)str);
-}
-
-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;
- }
-}
-
-void Window_Show(void) {
- ShowWindow(win_handle, SW_SHOW);
- BringWindowToTop(win_handle);
- SetForegroundWindow(win_handle);
-}
-
-int Window_GetWindowState(void) {
- DWORD s = GetWindowLongW(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 */
- SetWindowLongW(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();
- }
- 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;
-}
-
-
-void Window_SetSize(int width, int height) {
- DWORD style = GetWindowLongW(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_Close(void) {
- PostMessageW(win_handle, WM_CLOSE, 0, 0);
-}
-
-void Window_ProcessEvents(void) {
- 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) {
- WindowInfo.Focused = foreground == win_handle;
- }
-}
-
-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 */
- if (visible) {
- for (i = 0; i < 10 && ShowCursor(true) < 0; i++) { }
- } else {
- for (i = 0; i < 10 && ShowCursor(false) >= 0; i++) {}
- }
-}
-
-static void ShowDialogCore(const char* title, const char* msg) {
- MessageBoxA(win_handle, msg, title, 0);
-}
-
-static HDC draw_DC;
-static HBITMAP draw_DIB;
-void Window_AllocFramebuffer(struct Bitmap* bmp) {
- 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 = bmp->width;
- hdr.bmiHeader.biHeight = -bmp->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");
-}
-
-void Window_DrawFramebuffer(Rect2D r) {
- 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) {
- static const cc_string user32 = String_FromConst("USER32.DLL");
- void* lib;
- RAWINPUTDEVICE rid;
-
- if ((lib = DynamicLib_Load2(&user32))) {
- _registerRawInput = (FUNC_RegisterRawInput)DynamicLib_Get2(lib, "RegisterRawInputDevices");
- _getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
- rawMouseSupported = _registerRawInput && _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 (_registerRawInput(&rid, 1, sizeof(rid))) return;
- Logger_SysWarn(GetLastError(), "initing raw mouse");
- rawMouseSupported = false;
-}
-
-void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
-void Window_SetKeyboardText(const cc_string* text) { }
-void Window_CloseKeyboard(void) { }
-
-void Window_EnableRawMouse(void) {
- DefaultEnableRawMouse();
- if (!rawMouseInited) InitRawMouse();
- rawMouseInited = true;
-}
-
-void Window_UpdateRawMouse(void) {
- if (rawMouseSupported) {
- /* handled in WM_INPUT messages */
- CentreMousePosition();
- } else {
- DefaultUpdateRawMouse();
- }
-}
-
-void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
-
-
-/*########################################################################################################################*
-*-------------------------------------------------------X11 window--------------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_X11
-#include "_WindowBase.h"
-#include
-#include
-#include
-#include
-
-#ifdef X_HAVE_UTF8_STRING
-#define CC_BUILD_XIM
-/* XIM support based off details described in */
-/* https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/ */
-#endif
-
-#define _NET_WM_STATE_REMOVE 0
-#define _NET_WM_STATE_ADD 1
-#define _NET_WM_STATE_TOGGLE 2
-
-static Display* win_display;
-static Window win_rootWin, win_handle;
-static XVisualInfo win_visual;
-#ifdef CC_BUILD_XIM
-static XIM win_xim;
-static XIC win_xic;
-#endif
-
-static Atom wm_destroy, net_wm_state, net_wm_ping;
-static Atom net_wm_state_minimized;
-static Atom net_wm_state_fullscreen;
-
-static Atom xa_clipboard, xa_targets, xa_utf8_string, xa_data_sel;
-static Atom xa_atom = 4;
-static cc_bool grabCursor;
-static long win_eventMask = StructureNotifyMask | /* SubstructureNotifyMask | */
- ExposureMask | KeyReleaseMask | KeyPressMask | KeymapStateMask |
- PointerMotionMask | FocusChangeMask | ButtonPressMask | ButtonReleaseMask |
- EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
-
-static int MapNativeKey(KeySym key, unsigned int state) {
- if (key >= XK_0 && key <= XK_9) { return '0' + (key - XK_0); }
- if (key >= XK_A && key <= XK_Z) { return 'A' + (key - XK_A); }
- if (key >= XK_a && key <= XK_z) { return 'A' + (key - XK_a); }
-
- if (key >= XK_F1 && key <= XK_F24) { return KEY_F1 + (key - XK_F1); }
- if (key >= XK_KP_0 && key <= XK_KP_9) { return KEY_KP0 + (key - XK_KP_0); }
-
- /* Same Num Lock behaviour as Windows and text editors */
- if (key >= XK_KP_Home && key <= XK_KP_Delete && !(state & Mod2Mask)) {
- if (key == XK_KP_Home) return KEY_HOME;
- if (key == XK_KP_Up) return KEY_UP;
- if (key == XK_KP_Page_Up) return KEY_PAGEUP;
-
- if (key == XK_KP_Left) return KEY_LEFT;
- if (key == XK_KP_Insert) return KEY_INSERT;
- if (key == XK_KP_Right) return KEY_RIGHT;
-
- if (key == XK_KP_End) return KEY_END;
- if (key == XK_KP_Down) return KEY_DOWN;
- if (key == XK_KP_Page_Down) return KEY_PAGEDOWN;
- }
-
- /* A chromebook user reported issues with pressing some keys: */
- /* tilde - "Unknown key press: (8000060, 800007E) */
- /* quote - "Unknown key press: (8000027, 8000022) */
- /* Note if 8000 is stripped, you get '0060' (XK_grave) and 0027 (XK_apostrophe) */
- /* ChromeOS seems to also mask to 0xFFFF, so I also do so here */
- /* https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_x.cc */
- key &= 0xFFFF;
-
- switch (key) {
- case XK_Escape: return KEY_ESCAPE;
- case XK_Return: return KEY_ENTER;
- case XK_space: return KEY_SPACE;
- case XK_BackSpace: return KEY_BACKSPACE;
-
- case XK_Shift_L: return KEY_LSHIFT;
- case XK_Shift_R: return KEY_RSHIFT;
- case XK_Alt_L: return KEY_LALT;
- case XK_Alt_R: return KEY_RALT;
- case XK_Control_L: return KEY_LCTRL;
- case XK_Control_R: return KEY_RCTRL;
- case XK_Super_L: return KEY_LWIN;
- case XK_Super_R: return KEY_RWIN;
- case XK_Meta_L: return KEY_LWIN;
- case XK_Meta_R: return KEY_RWIN;
-
- case XK_Menu: return KEY_MENU;
- case XK_Tab: return KEY_TAB;
- case XK_minus: return KEY_MINUS;
- case XK_plus: return KEY_EQUALS;
- case XK_equal: return KEY_EQUALS;
-
- case XK_Caps_Lock: return KEY_CAPSLOCK;
- case XK_Num_Lock: return KEY_NUMLOCK;
-
- case XK_Pause: return KEY_PAUSE;
- case XK_Break: return KEY_PAUSE;
- case XK_Scroll_Lock: return KEY_SCROLLLOCK;
- case XK_Insert: return KEY_INSERT;
- case XK_Print: return KEY_PRINTSCREEN;
- case XK_Sys_Req: return KEY_PRINTSCREEN;
-
- case XK_backslash: return KEY_BACKSLASH;
- case XK_bar: return KEY_BACKSLASH;
- case XK_braceleft: return KEY_LBRACKET;
- case XK_bracketleft: return KEY_LBRACKET;
- case XK_braceright: return KEY_RBRACKET;
- case XK_bracketright: return KEY_RBRACKET;
- case XK_colon: return KEY_SEMICOLON;
- case XK_semicolon: return KEY_SEMICOLON;
- case XK_quoteright: return KEY_QUOTE;
- case XK_quotedbl: return KEY_QUOTE;
- case XK_quoteleft: return KEY_TILDE;
- case XK_asciitilde: return KEY_TILDE;
-
- case XK_comma: return KEY_COMMA;
- case XK_less: return KEY_COMMA;
- case XK_period: return KEY_PERIOD;
- case XK_greater: return KEY_PERIOD;
- case XK_slash: return KEY_SLASH;
- case XK_question: return KEY_SLASH;
-
- case XK_Left: return KEY_LEFT;
- case XK_Down: return KEY_DOWN;
- case XK_Right: return KEY_RIGHT;
- case XK_Up: return KEY_UP;
-
- case XK_Delete: return KEY_DELETE;
- case XK_Home: return KEY_HOME;
- case XK_End: return KEY_END;
- case XK_Page_Up: return KEY_PAGEUP;
- case XK_Page_Down: return KEY_PAGEDOWN;
-
- case XK_KP_Add: return KEY_KP_PLUS;
- case XK_KP_Subtract: return KEY_KP_MINUS;
- case XK_KP_Multiply: return KEY_KP_MULTIPLY;
- case XK_KP_Divide: return KEY_KP_DIVIDE;
- case XK_KP_Decimal: return KEY_KP_DECIMAL;
- case XK_KP_Insert: return KEY_KP0;
- case XK_KP_End: return KEY_KP1;
- case XK_KP_Down: return KEY_KP2;
- case XK_KP_Page_Down: return KEY_KP3;
- case XK_KP_Left: return KEY_KP4;
- case XK_KP_Begin: return KEY_KP5;
- case XK_KP_Right: return KEY_KP6;
- case XK_KP_Home: return KEY_KP7;
- case XK_KP_Up: return KEY_KP8;
- case XK_KP_Page_Up: return KEY_KP9;
- case XK_KP_Delete: return KEY_KP_DECIMAL;
- case XK_KP_Enter: return KEY_KP_ENTER;
- }
- return KEY_NONE;
-}
-
-/* NOTE: This may not be entirely accurate, because user can configure keycode mappings */
-static const cc_uint8 keycodeMap[136] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, '1', '2', '3', '4', '5', '6',
- '7', '8', '9', '0', KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
- 'O', 'P', KEY_LBRACKET, KEY_RBRACKET, KEY_ENTER, KEY_LCTRL, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', KEY_SEMICOLON,
- KEY_QUOTE, KEY_TILDE, KEY_LSHIFT, KEY_BACKSLASH, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', KEY_PERIOD, KEY_COMMA, KEY_SLASH, KEY_RSHIFT, KEY_KP_MULTIPLY,
- KEY_LALT, KEY_SPACE, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_KP7,
- KEY_KP8, KEY_KP9, KEY_KP_MINUS, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP_PLUS, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP0, KEY_KP_DECIMAL, 0, 0, 0, KEY_F11,
- KEY_F12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- KEY_RALT, KEY_RCTRL, KEY_HOME, KEY_UP, KEY_PAGEUP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, 0, 0, 0, 0,
- 0, 0, 0, KEY_PAUSE, 0, 0, 0, 0, 0, KEY_LWIN, 0, KEY_RWIN
-};
-
-static int MapNativeKeycode(unsigned int keycode) {
- return keycode < Array_Elems(keycodeMap) ? keycodeMap[keycode] : 0;
-}
-
-static void RegisterAtoms(void) {
- Display* display = win_display;
- wm_destroy = XInternAtom(display, "WM_DELETE_WINDOW", true);
- net_wm_state = XInternAtom(display, "_NET_WM_STATE", false);
- net_wm_ping = XInternAtom(display, "_NET_WM_PING", false);
- net_wm_state_minimized = XInternAtom(display, "_NET_WM_STATE_MINIMIZED", false);
- net_wm_state_fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", false);
-
- xa_clipboard = XInternAtom(display, "CLIPBOARD", false);
- xa_targets = XInternAtom(display, "TARGETS", false);
- xa_utf8_string = XInternAtom(display, "UTF8_STRING", false);
- xa_data_sel = XInternAtom(display, "CC_SEL_DATA", false);
-}
-
-static void RefreshWindowBounds(int width, int height) {
- if (width != WindowInfo.Width || height != WindowInfo.Height) {
- WindowInfo.Width = width;
- WindowInfo.Height = height;
- Event_RaiseVoid(&WindowEvents.Resized);
- }
-}
-
-typedef int (*X11_ErrorHandler)(Display* dpy, XErrorEvent* ev);
-typedef int (*X11_IOErrorHandler)(Display* dpy);
-static X11_ErrorHandler realXErrorHandler;
-static X11_IOErrorHandler realXIOErrorHandler;
-
-static void LogXErrorCore(const char* msg) {
- char traceBuffer[2048];
- cc_string trace;
- Platform_LogConst(msg);
-
- String_InitArray(trace, traceBuffer);
- Logger_Backtrace(&trace, NULL);
- Platform_Log(traceBuffer, trace.length);
-}
-
-static int LogXError(Display* dpy, XErrorEvent* ev) {
- LogXErrorCore("== unhandled X11 error ==");
- return realXErrorHandler(dpy, ev);
-}
-
-static int LogXIOError(Display* dpy) {
- LogXErrorCore("== unhandled XIO error ==");
- return realXIOErrorHandler(dpy);
-}
-
-static void HookXErrors(void) {
- realXErrorHandler = XSetErrorHandler(LogXError);
- realXIOErrorHandler = XSetIOErrorHandler(LogXIOError);
-}
-
-
-/*########################################################################################################################*
-*--------------------------------------------------Public implementation--------------------------------------------------*
-*#########################################################################################################################*/
-static XVisualInfo GLContext_SelectVisual(void);
-void Window_Init(void) {
- Display* display = XOpenDisplay(NULL);
- int screen;
-
- if (!display) Logger_Abort("Failed to open display");
- screen = DefaultScreen(display);
- HookXErrors();
-
- win_display = display;
- win_rootWin = RootWindow(display, screen);
-
- /* TODO: Use Xinerama and XRandR for querying these */
- DisplayInfo.Width = DisplayWidth(display, screen);
- DisplayInfo.Height = DisplayHeight(display, screen);
- DisplayInfo.Depth = DefaultDepth(display, screen);
- DisplayInfo.ScaleX = 1;
- DisplayInfo.ScaleY = 1;
-}
-
-#ifdef CC_BUILD_ICON
-extern const long CCIcon_Data[];
-extern const int CCIcon_Size;
-
-static void ApplyIcon(void) {
- Atom net_wm_icon = XInternAtom(win_display, "_NET_WM_ICON", false);
- Atom xa_cardinal = XInternAtom(win_display, "CARDINAL", false);
- XChangeProperty(win_display, win_handle, net_wm_icon, xa_cardinal, 32, PropModeReplace, CCIcon_Data, CCIcon_Size);
-}
-#else
-static void ApplyIcon(void) { }
-#endif
-
-void Window_Create(int width, int height) {
- XSetWindowAttributes attributes = { 0 };
- XSizeHints hints = { 0 };
- Atom protocols[2];
- int supported, x, y;
-
- x = Display_CentreX(width);
- y = Display_CentreY(height);
- RegisterAtoms();
- win_visual = GLContext_SelectVisual();
-
- Platform_LogConst("Opening render window... ");
- attributes.colormap = XCreateColormap(win_display, win_rootWin, win_visual.visual, AllocNone);
- attributes.event_mask = win_eventMask;
-
- win_handle = XCreateWindow(win_display, win_rootWin, x, y, width, height,
- 0, win_visual.depth /* CopyFromParent*/, InputOutput, win_visual.visual,
- CWColormap | CWEventMask | CWBackPixel | CWBorderPixel, &attributes);
- if (!win_handle) Logger_Abort("XCreateWindow failed");
-
-#ifdef CC_BUILD_XIM
- win_xim = XOpenIM(win_display, NULL, NULL, NULL);
- win_xic = XCreateIC(win_xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
- XNClientWindow, win_handle, NULL);
-#endif
-
- /* Set hints to try to force WM to create window at requested x,y */
- /* Without this, some WMs will instead place the window whereever */
- hints.base_width = width;
- hints.base_height = height;
- hints.flags = PSize | PPosition;
- XSetWMNormalHints(win_display, win_handle, &hints);
-
- /* Register for window destroy notification */
- protocols[0] = wm_destroy;
- protocols[1] = net_wm_ping;
- XSetWMProtocols(win_display, win_handle, protocols, 2);
-
- /* Request that auto-repeat is only set on devices that support it physically.
- This typically means that it's turned off for keyboards (which is what we want).
- We prefer this method over XAutoRepeatOff/On, because the latter needs to
- be reset before the program exits. */
- XkbSetDetectableAutoRepeat(win_display, true, &supported);
-
- RefreshWindowBounds(width, height);
- WindowInfo.Exists = true;
- WindowInfo.Handle = (void*)win_handle;
- grabCursor = Options_GetBool(OPT_GRAB_CURSOR, false);
-
- /* So right name appears in e.g. Ubuntu Unity launchbar */
- XClassHint hint = { 0 };
- hint.res_name = GAME_APP_TITLE;
- hint.res_class = GAME_APP_TITLE;
- XSetClassHint(win_display, win_handle, &hint);
- ApplyIcon();
-}
-
-void Window_SetTitle(const cc_string* title) {
- char str[NATIVE_STR_LEN];
- Platform_EncodeUtf8(str, title);
- XStoreName(win_display, win_handle, str);
-}
-
-static char clipboard_copy_buffer[256];
-static char clipboard_paste_buffer[256];
-static cc_string clipboard_copy_text = String_FromArray(clipboard_copy_buffer);
-static cc_string clipboard_paste_text = String_FromArray(clipboard_paste_buffer);
-static cc_bool clipboard_paste_received;
-
-void Clipboard_GetText(cc_string* value) {
- Window owner = XGetSelectionOwner(win_display, xa_clipboard);
- int i;
- if (!owner) return; /* no window owner */
-
- XConvertSelection(win_display, xa_clipboard, xa_utf8_string, xa_data_sel, win_handle, 0);
- clipboard_paste_received = false;
- clipboard_paste_text.length = 0;
-
- /* wait up to 1 second for SelectionNotify event to arrive */
- for (i = 0; i < 100; i++) {
- Window_ProcessEvents();
- if (clipboard_paste_received) {
- String_AppendString(value, &clipboard_paste_text);
- return;
- } else {
- Thread_Sleep(10);
- }
- }
-}
-
-void Clipboard_SetText(const cc_string* value) {
- String_Copy(&clipboard_copy_text, value);
- XSetSelectionOwner(win_display, xa_clipboard, win_handle, 0);
-}
-
-void Window_Show(void) { XMapWindow(win_display, win_handle); }
-
-int Window_GetWindowState(void) {
- cc_bool fullscreen = false, minimised = false;
- Atom prop_type;
- unsigned long items, after;
- int i, prop_format;
- Atom* data = NULL;
-
- XGetWindowProperty(win_display, win_handle,
- net_wm_state, 0, 256, false, xa_atom, &prop_type,
- &prop_format, &items, &after, &data);
-
- if (data) {
- for (i = 0; i < items; i++) {
- Atom atom = data[i];
-
- if (atom == net_wm_state_minimized) {
- minimised = true;
- } else if (atom == net_wm_state_fullscreen) {
- fullscreen = true;
- }
- }
- XFree(data);
- }
-
- if (fullscreen) return WINDOW_STATE_FULLSCREEN;
- if (minimised) return WINDOW_STATE_MINIMISED;
- return WINDOW_STATE_NORMAL;
-}
-
-static void ToggleFullscreen(long op) {
- XEvent ev = { 0 };
- ev.xclient.type = ClientMessage;
- ev.xclient.window = win_handle;
- ev.xclient.message_type = net_wm_state;
- ev.xclient.format = 32;
- ev.xclient.data.l[0] = op;
- ev.xclient.data.l[1] = net_wm_state_fullscreen;
-
- XSendEvent(win_display, win_rootWin, false,
- SubstructureRedirectMask | SubstructureNotifyMask, &ev);
- XSync(win_display, false);
- XRaiseWindow(win_display, win_handle);
- Window_ProcessEvents();
-}
-
-cc_result Window_EnterFullscreen(void) {
- ToggleFullscreen(_NET_WM_STATE_ADD); return 0;
-}
-cc_result Window_ExitFullscreen(void) {
- ToggleFullscreen(_NET_WM_STATE_REMOVE); return 0;
-}
-
-void Window_SetSize(int width, int height) {
- XResizeWindow(win_display, win_handle, width, height);
- Window_ProcessEvents();
-}
-
-void Window_Close(void) {
- XEvent ev = { 0 };
- ev.type = ClientMessage;
- ev.xclient.format = 32;
- ev.xclient.display = win_display;
- ev.xclient.window = win_handle;
- ev.xclient.data.l[0] = wm_destroy;
-
- XSendEvent(win_display, win_handle, false, 0, &ev);
- XFlush(win_display);
-}
-
-static int MapNativeMouse(int button) {
- if (button == 1) return KEY_LMOUSE;
- if (button == 2) return KEY_MMOUSE;
- if (button == 3) return KEY_RMOUSE;
- if (button == 8) return KEY_XBUTTON1;
- if (button == 9) return KEY_XBUTTON2;
- return 0;
-}
-
-static int TryGetKey(XKeyEvent* ev) {
- KeySym keysym1 = XLookupKeysym(ev, 0);
- KeySym keysym2 = XLookupKeysym(ev, 1);
-
- int key = MapNativeKey(keysym1, ev->state);
- if (!key) key = MapNativeKey(keysym2, ev->state);
- if (key) return key;
-
- Platform_Log3("Unknown key %i (%x, %x)", &ev->keycode, &keysym1, &keysym2);
- /* The user may be using a keyboard layout such as cryllic - */
- /* fallback to trying to conver the raw scancodes instead */
- return MapNativeKeycode(ev->keycode);
-}
-
-static Atom Window_GetSelectionProperty(XEvent* e) {
- Atom prop = e->xselectionrequest.property;
- if (prop) return prop;
-
- /* For obsolete clients. See ICCCM spec, selections chapter for reasoning. */
- return e->xselectionrequest.target;
-}
-
-static Bool FilterEvent(Display* d, XEvent* e, XPointer w) {
- return
- e->xany.window == (Window)w ||
- !e->xany.window || /* KeymapNotify events don't have a window */
- e->type == GenericEvent; /* For XInput events */
-}
-
-static void HandleWMDestroy(void) {
- Platform_LogConst("Exit message received.");
- Event_RaiseVoid(&WindowEvents.Closing);
-
- /* sync and discard all events queued */
- XSync(win_display, true);
- XDestroyWindow(win_display, win_handle);
- WindowInfo.Exists = false;
-}
-
-static void HandleWMPing(XEvent* e) {
- e->xany.window = win_rootWin;
- XSendEvent(win_display, win_rootWin, false,
- SubstructureRedirectMask | SubstructureNotifyMask, e);
-}
-static void HandleGenericEvent(XEvent* e);
-
-void Window_ProcessEvents(void) {
- XEvent e;
- Window focus;
- int focusRevert;
- int i, btn, key, status;
-
- while (WindowInfo.Exists) {
- if (!XCheckIfEvent(win_display, &e, FilterEvent, (XPointer)win_handle)) break;
- if (XFilterEvent(&e, None) == True) continue;
-
- switch (e.type) {
- case GenericEvent:
- HandleGenericEvent(&e); break;
- case ClientMessage:
- if (e.xclient.data.l[0] == wm_destroy) {
- HandleWMDestroy();
- } else if (e.xclient.data.l[0] == net_wm_ping) {
- HandleWMPing(&e);
- }
- break;
-
- case DestroyNotify:
- Platform_LogConst("Window destroyed");
- WindowInfo.Exists = false;
- break;
-
- case ConfigureNotify:
- RefreshWindowBounds(e.xconfigure.width, e.xconfigure.height);
- break;
-
- case Expose:
- if (e.xexpose.count == 0) Event_RaiseVoid(&WindowEvents.Redraw);
- break;
-
- case LeaveNotify:
- XGetInputFocus(win_display, &focus, &focusRevert);
- if (focus == PointerRoot) {
- WindowInfo.Focused = false; Event_RaiseVoid(&WindowEvents.FocusChanged);
- }
- break;
-
- case EnterNotify:
- XGetInputFocus(win_display, &focus, &focusRevert);
- if (focus == PointerRoot) {
- WindowInfo.Focused = true; Event_RaiseVoid(&WindowEvents.FocusChanged);
- }
- break;
-
- case KeyPress:
- {
- char data[64], c;
- key = TryGetKey(&e.xkey);
- if (key) Input_SetPressed(key);
-
-#ifdef CC_BUILD_XIM
- cc_codepoint cp;
- char* chars = data;
-
- status = Xutf8LookupString(win_xic, &e.xkey, data, Array_Elems(data), NULL, NULL);
- for (; status > 0; status -= i) {
- i = Convert_Utf8ToCodepoint(&cp, chars, status);
- if (!i) break;
-
- if (Convert_TryCodepointToCP437(cp, &c)) Event_RaiseInt(&InputEvents.Press, c);
- chars += i;
- }
-#else
- /* This only really works for latin keys (e.g. so some finnish keys still work) */
- status = XLookupString(&e.xkey, data, Array_Elems(data), NULL, NULL);
- for (i = 0; i < status; i++) {
- if (!Convert_TryCodepointToCP437((cc_uint8)data[i], &c)) continue;
- Event_RaiseInt(&InputEvents.Press, c);
- }
-#endif
- } break;
-
- case KeyRelease:
- key = TryGetKey(&e.xkey);
- if (key) Input_SetReleased(key);
- break;
-
- case ButtonPress:
- btn = MapNativeMouse(e.xbutton.button);
- if (btn) Input_SetPressed(btn);
- else if (e.xbutton.button == 4) Mouse_ScrollWheel(+1);
- else if (e.xbutton.button == 5) Mouse_ScrollWheel(-1);
- break;
-
- case ButtonRelease:
- btn = MapNativeMouse(e.xbutton.button);
- if (btn) Input_SetReleased(btn);
- break;
-
- case MotionNotify:
- Pointer_SetPosition(0, e.xmotion.x, e.xmotion.y);
- break;
-
- case FocusIn:
- case FocusOut:
- /* Don't lose focus when another app grabs key or mouse */
- if (e.xfocus.mode == NotifyGrab || e.xfocus.mode == NotifyUngrab) break;
-
- WindowInfo.Focused = e.type == FocusIn;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
- /* TODO: Keep track of keyboard when focus is lost */
- if (!WindowInfo.Focused) Input_Clear();
- break;
-
- case MappingNotify:
- if (e.xmapping.request == MappingModifier || e.xmapping.request == MappingKeyboard) {
- Platform_LogConst("keybard mapping refreshed");
- XRefreshKeyboardMapping(&e.xmapping);
- }
- break;
-
- case PropertyNotify:
- if (e.xproperty.atom == net_wm_state) {
- Event_RaiseVoid(&WindowEvents.StateChanged);
- }
- break;
-
- case SelectionNotify:
- if (e.xselection.selection == xa_clipboard && e.xselection.target == xa_utf8_string && e.xselection.property == xa_data_sel) {
- Atom prop_type;
- int prop_format;
- unsigned long items, after;
- cc_uint8* data = NULL;
-
- XGetWindowProperty(win_display, win_handle, xa_data_sel, 0, 1024, false, 0,
- &prop_type, &prop_format, &items, &after, &data);
- XDeleteProperty(win_display, win_handle, xa_data_sel);
-
- if (data && items && prop_type == xa_utf8_string) {
- clipboard_paste_received = true;
- clipboard_paste_text.length = 0;
- String_AppendUtf8(&clipboard_paste_text, data, items);
- }
- if (data) XFree(data);
- }
- break;
-
- case SelectionRequest:
- {
- XEvent reply = { 0 };
- reply.xselection.type = SelectionNotify;
- reply.xselection.send_event = true;
- reply.xselection.display = win_display;
- reply.xselection.requestor = e.xselectionrequest.requestor;
- reply.xselection.selection = e.xselectionrequest.selection;
- reply.xselection.target = e.xselectionrequest.target;
- reply.xselection.property = 0;
- reply.xselection.time = e.xselectionrequest.time;
-
- if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_utf8_string && clipboard_copy_text.length) {
- reply.xselection.property = Window_GetSelectionProperty(&e);
- char str[800];
- int len = Platform_EncodeUtf8(str, &clipboard_copy_text);
-
- XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_utf8_string, 8,
- PropModeReplace, (unsigned char*)str, len);
- } else if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_targets) {
- reply.xselection.property = Window_GetSelectionProperty(&e);
-
- Atom data[2] = { xa_utf8_string, xa_targets };
- XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_atom, 32,
- PropModeReplace, (unsigned char*)data, 2);
- }
- XSendEvent(win_display, e.xselectionrequest.requestor, true, 0, &reply);
- } break;
- }
- }
-}
-
-static void Cursor_GetRawPos(int* x, int* y) {
- Window rootW, childW;
- int childX, childY;
- unsigned int mask;
- XQueryPointer(win_display, win_rootWin, &rootW, &childW, x, y, &childX, &childY, &mask);
-}
-
-void Cursor_SetPosition(int x, int y) {
- XWarpPointer(win_display, None, win_handle, 0, 0, 0, 0, x, y);
- XFlush(win_display); /* TODO: not sure if XFlush call is necessary */
-}
-
-static Cursor blankCursor;
-static void Cursor_DoSetVisible(cc_bool visible) {
- if (visible) {
- XUndefineCursor(win_display, win_handle);
- } else {
- if (!blankCursor) {
- char data = 0;
- XColor col = { 0 };
- Pixmap pixmap = XCreateBitmapFromData(win_display, win_handle, &data, 1, 1);
- blankCursor = XCreatePixmapCursor(win_display, pixmap, pixmap, &col, &col, 0, 0);
- XFreePixmap(win_display, pixmap);
- }
- XDefineCursor(win_display, win_handle, blankCursor);
- }
-}
-
-
-/*########################################################################################################################*
-*-----------------------------------------------------X11 message box-----------------------------------------------------*
-*#########################################################################################################################*/
-struct X11MessageBox {
- Window win;
- Display* dpy;
- GC gc;
- unsigned long white, black, background;
- unsigned long btnBorder, highlight, shadow;
-};
-
-static unsigned long X11_Col(struct X11MessageBox* m, cc_uint8 r, cc_uint8 g, cc_uint8 b) {
- Colormap cmap = XDefaultColormap(m->dpy, DefaultScreen(m->dpy));
- XColor col = { 0 };
- col.red = r << 8;
- col.green = g << 8;
- col.blue = b << 8;
- col.flags = DoRed | DoGreen | DoBlue;
-
- XAllocColor(m->dpy, cmap, &col);
- return col.pixel;
-}
-
-static void X11MessageBox_Init(struct X11MessageBox* m) {
- m->black = BlackPixel(m->dpy, DefaultScreen(m->dpy));
- m->white = WhitePixel(m->dpy, DefaultScreen(m->dpy));
- m->background = X11_Col(m, 206, 206, 206);
-
- m->btnBorder = X11_Col(m, 60, 60, 60);
- m->highlight = X11_Col(m, 144, 144, 144);
- m->shadow = X11_Col(m, 49, 49, 49);
-
- m->win = XCreateSimpleWindow(m->dpy, DefaultRootWindow(m->dpy), 0, 0, 100, 100,
- 0, m->black, m->background);
- XSelectInput(m->dpy, m->win, ExposureMask | StructureNotifyMask |
- KeyReleaseMask | PointerMotionMask |
- ButtonPressMask | ButtonReleaseMask );
-
- m->gc = XCreateGC(m->dpy, m->win, 0, NULL);
- XSetForeground(m->dpy, m->gc, m->black);
- XSetBackground(m->dpy, m->gc, m->background);
-}
-
-static void X11MessageBox_Free(struct X11MessageBox* m) {
- XFreeGC(m->dpy, m->gc);
- XDestroyWindow(m->dpy, m->win);
-}
-
-struct X11Textbox {
- int x, y, width, height;
- int lineHeight, descent;
- const char* text;
-};
-
-static void X11Textbox_Measure(struct X11Textbox* t, XFontStruct* font) {
- cc_string str = String_FromReadonly(t->text), line;
- XCharStruct overall;
- int direction, ascent, descent, lines = 0;
-
- for (; str.length; lines++) {
- String_UNSAFE_SplitBy(&str, '\n', &line);
- XTextExtents(font, line.buffer, line.length, &direction, &ascent, &descent, &overall);
- t->width = max(overall.width, t->width);
- }
-
- t->lineHeight = ascent + descent;
- t->descent = descent;
- t->height = t->lineHeight * lines;
-}
-
-static void X11Textbox_Draw(struct X11Textbox* t, struct X11MessageBox* m) {
- cc_string str = String_FromReadonly(t->text), line;
- int y = t->y + t->lineHeight - t->descent; /* TODO: is -descent even right? */
-
- for (; str.length; y += t->lineHeight) {
- String_UNSAFE_SplitBy(&str, '\n', &line);
- XDrawString(m->dpy, m->win, m->gc, t->x, y, line.buffer, line.length);
- }
-}
-
-struct X11Button {
- int x, y, width, height;
- cc_bool clicked;
- struct X11Textbox text;
-};
-
-static void X11Button_Draw(struct X11Button* b, struct X11MessageBox* m) {
- struct X11Textbox* t;
- int begX, endX, begY, endY;
-
- XSetForeground(m->dpy, m->gc, m->btnBorder);
- XDrawRectangle(m->dpy, m->win, m->gc, b->x, b->y,
- b->width, b->height);
-
- t = &b->text;
- begX = b->x + 1; endX = b->x + b->width - 1;
- begY = b->y + 1; endY = b->y + b->height - 1;
-
- if (b->clicked) {
- XSetForeground(m->dpy, m->gc, m->highlight);
- XDrawRectangle(m->dpy, m->win, m->gc, begX, begY,
- endX - begX, endY - begY);
- } else {
- XSetForeground(m->dpy, m->gc, m->white);
- XDrawLine(m->dpy, m->win, m->gc, begX, begY,
- endX - 1, begY);
- XDrawLine(m->dpy, m->win, m->gc, begX, begY,
- begX, endY - 1);
-
- XSetForeground(m->dpy, m->gc, m->highlight);
- XDrawLine(m->dpy, m->win, m->gc, begX + 1, endY - 1,
- endX - 1, endY - 1);
- XDrawLine(m->dpy, m->win, m->gc, endX - 1, begY + 1,
- endX - 1, endY - 1);
-
- XSetForeground(m->dpy, m->gc, m->shadow);
- XDrawLine(m->dpy, m->win, m->gc, begX, endY, endX, endY);
- XDrawLine(m->dpy, m->win, m->gc, endX, begY, endX, endY);
- }
-
- XSetForeground(m->dpy, m->gc, m->black);
- t->x = b->x + b->clicked + (b->width - t->width) / 2;
- t->y = b->y + b->clicked + (b->height - t->height) / 2;
- X11Textbox_Draw(t, m);
-}
-
-static int X11Button_Contains(struct X11Button* b, int x, int y) {
- return x >= b->x && x < (b->x + b->width) &&
- y >= b->y && y < (b->y + b->height);
-}
-
-static Bool X11_FilterEvent(Display* d, XEvent* e, XPointer w) { return e->xany.window == (Window)w; }
-static void X11_MessageBox(const char* title, const char* text, struct X11MessageBox* m) {
- struct X11Button ok = { 0 };
- struct X11Textbox body = { 0 };
-
- Atom protocols[2];
- XFontStruct* font;
- int x, y, width, height;
- XSizeHints hints = { 0 };
- int mouseX = -1, mouseY = -1, over;
- XEvent e;
-
- X11MessageBox_Init(m);
- XMapWindow(m->dpy, m->win);
- XStoreName(m->dpy, m->win, title);
-
- protocols[0] = XInternAtom(m->dpy, "WM_DELETE_WINDOW", false);
- protocols[1] = XInternAtom(m->dpy, "_NET_WM_PING", false);
- XSetWMProtocols(m->dpy, m->win, protocols, 2);
-
- font = XQueryFont(m->dpy, XGContextFromGC(m->gc));
- if (!font) return;
-
- /* Compute size of widgets */
- body.text = text;
- X11Textbox_Measure(&body, font);
- ok.text.text = "OK";
- X11Textbox_Measure(&ok.text, font);
- ok.width = ok.text.width + 70;
- ok.height = ok.text.height + 10;
-
- /* Compute size and position of window */
- width = body.width + 20;
- height = body.height + 20 + ok.height + 20;
- x = DisplayWidth (m->dpy, DefaultScreen(m->dpy))/2 - width/2;
- y = DisplayHeight(m->dpy, DefaultScreen(m->dpy))/2 - height/2;
- XMoveResizeWindow(m->dpy, m->win, x, y, width, height);
-
- /* Adjust bounds of widgets */
- body.x = 10; body.y = 10;
- ok.x = width/2 - ok.width/2;
- ok.y = height - ok.height - 10;
-
- /* This marks the window as popup window of the main window */
- /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */
- /* Depending on WM, removes minimise and doesn't show in taskbar */
- if (win_handle) XSetTransientForHint(m->dpy, m->win, win_handle);
-
- XFreeFontInfo(NULL, font, 1);
- XUnmapWindow(m->dpy, m->win); /* Make window non resizeable */
-
- hints.flags = PSize | PMinSize | PMaxSize;
- hints.min_width = hints.max_width = hints.base_width = width;
- hints.min_height = hints.max_height = hints.base_height = height;
-
- XSetWMNormalHints(m->dpy, m->win, &hints);
- XMapRaised(m->dpy, m->win);
- XFlush(m->dpy);
-
- for (;;) {
- /* The naive solution is to use XNextEvent(m->dpy, &e) here. */
- /* However this causes issues as that removes events that */
- /* should have been delivered to the main game window. */
- /* (e.g. breaks initial window resize with i3 WM) */
- XIfEvent(m->dpy, &e, X11_FilterEvent, (XPointer)m->win);
-
- switch (e.type)
- {
- case ButtonPress:
- case ButtonRelease:
- if (e.xbutton.button != Button1) break;
- over = X11Button_Contains(&ok, mouseX, mouseY);
-
- if (ok.clicked && e.type == ButtonRelease) {
- if (over) return;
- }
- ok.clicked = e.type == ButtonPress && over;
- /* fallthrough to redraw window */
-
- case Expose:
- case MapNotify:
- XClearWindow(m->dpy, m->win);
- X11Textbox_Draw(&body, m);
- X11Button_Draw(&ok, m);
- XFlush(m->dpy);
- break;
-
- case KeyRelease:
- if (XLookupKeysym(&e.xkey, 0) == XK_Escape) return;
- break;
-
- case ClientMessage:
- /* { WM_DELETE_WINDOW, _NET_WM_PING } */
- if (e.xclient.data.l[0] == protocols[0]) return;
- if (e.xclient.data.l[0] == protocols[1]) HandleWMPing(&e);
- break;
-
- case MotionNotify:
- mouseX = e.xmotion.x; mouseY = e.xmotion.y;
- break;
- }
- }
-}
-
-static void ShowDialogCore(const char* title, const char* msg) {
- struct X11MessageBox m = { 0 };
- m.dpy = win_display;
-
- /* Failing to create a display means can't display a message box. */
- /* However the user might have launched the game through terminal, */
- /* so fallback to console instead of just dying from a segfault */
- if (!m.dpy) {
- Platform_LogConst("### MESSAGE ###");
- Platform_LogConst(title);
- Platform_LogConst(msg);
- return;
- }
-
- X11_MessageBox(title, msg, &m);
- X11MessageBox_Free(&m);
- XFlush(m.dpy); /* flush so window disappears immediately */
-}
-
-static GC fb_gc;
-static XImage* fb_image;
-void Window_AllocFramebuffer(struct Bitmap* bmp) {
- if (!fb_gc) fb_gc = XCreateGC(win_display, win_handle, 0, NULL);
-
- bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
- fb_image = XCreateImage(win_display, win_visual.visual,
- win_visual.depth, ZPixmap, 0, (char*)bmp->scan0,
- bmp->width, bmp->height, 32, 0);
-}
-
-void Window_DrawFramebuffer(Rect2D r) {
- XPutImage(win_display, win_handle, fb_gc, fb_image,
- r.X, r.Y, r.X, r.Y, r.Width, r.Height);
-}
-
-void Window_FreeFramebuffer(struct Bitmap* bmp) {
- XFree(fb_image);
- Mem_Free(bmp->scan0);
-}
-
-void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
-void Window_SetKeyboardText(const cc_string* text) { }
-void Window_CloseKeyboard(void) { }
-
-static cc_bool rawMouseInited, rawMouseSupported;
-static int xiOpcode;
-
-static void CheckMovementDelta(double dx, double dy) {
- /* Despite the assumption that XI_RawMotion is relative, */
- /* unfortunately there's a few buggy corner cases out there */
- /* where absolute coordinates are provided instead. */
- /* The ugly code belows tries to detect these corner cases, */
- /* and disables XInput2 when that happens */
- static int valid, fails;
-
- if (valid) return;
- /* The default window resolution is 854 x 480, so if there's */
- /* a delta less than half of that, then it's almost certain */
- /* that the provided coordinates are relative.*/
- if (dx < 300 || dy < 200) { valid = true; return; }
-
- if (fails++ < 20) return;
- /* Checked over 20 times now, but no relative coordinates, */
- /* so give up trying to use XInput2 anymore. */
- Platform_LogConst("Buggy XInput2 detected, disabling it..");
- rawMouseSupported = false;
-}
-
-static void HandleGenericEvent(XEvent* e) {
- const double* values;
- XIRawEvent* ev;
- double dx, dy;
-
- if (!rawMouseSupported || e->xcookie.extension != xiOpcode) return;
- if (!XGetEventData(win_display, &e->xcookie)) return;
-
- if (e->xcookie.evtype == XI_RawMotion && Input_RawMode) {
- ev = (XIRawEvent*)e->xcookie.data;
- values = ev->raw_values;
-
- /* Raw motion events may not always have values for both axes */
- dx = XIMaskIsSet(ev->valuators.mask, 0) ? *values++ : 0;
- dy = XIMaskIsSet(ev->valuators.mask, 1) ? *values++ : 0;
-
- CheckMovementDelta(dx, dy);
- /* Using 0.5f here makes the sensitivity about same as normal cursor movement */
- Event_RaiseRawMove(&PointerEvents.RawMoved, dx * 0.5f, dy * 0.5f);
- }
- XFreeEventData(win_display, &e->xcookie);
-}
-
-static void InitRawMouse(void) {
- XIEventMask evmask;
- unsigned char masks[XIMaskLen(XI_LASTEVENT)] = { 0 };
- int ev, err, major, minor;
-
- if (!XQueryExtension(win_display, "XInputExtension", &xiOpcode, &ev, &err)) {
- Platform_LogConst("XInput unsupported");
- return;
- }
-
- /* Only XInput 2.0 is actually required. However, 2.0 has the annoying */
- /* behaviour where raw input is NOT delivered while pointer is grabbed. */
- /* (i.e. if you press mouse button, no more raw mouse movement events) */
- /* http://wine.1045685.n8.nabble.com/PATCH-0-9-Implement-DInput8-mouse-using-RawInput-and-XInput2-RawEvents-only-td6016923.html */
- /* Thankfully XInput >= 2.1 corrects this behaviour */
- /* http://who-t.blogspot.com/2011/09/whats-new-in-xi-21-raw-events.html */
- major = 2; minor = 2;
- if (XIQueryVersion(win_display, &major, &minor) != Success) {
- Platform_Log2("Only XInput %i.%i supported", &major, &minor);
- return;
- }
-
- /* Sometimes XIQueryVersion will report Success, even though the */
- /* supported version is only 2.0! So make sure to handle that. */
- if (major < 2 || minor < 2) {
- Platform_Log2("Only XInput %i.%i supported", &major, &minor);
- return;
- }
-
- XISetMask(masks, XI_RawMotion);
- evmask.deviceid = XIAllMasterDevices;
- evmask.mask_len = sizeof(masks);
- evmask.mask = masks;
-
- XISelectEvents(win_display, win_rootWin, &evmask, 1);
- rawMouseSupported = true;
-}
-
-void Window_EnableRawMouse(void) {
- DefaultEnableRawMouse();
- if (!rawMouseInited) InitRawMouse();
- rawMouseInited = true;
-
- if (!grabCursor) return;
- XGrabPointer(win_display, win_handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, win_handle, blankCursor, CurrentTime);
-}
-
-void Window_UpdateRawMouse(void) {
- if (rawMouseSupported) {
- /* Handled by XI_RawMotion generic event */
- CentreMousePosition();
- } else {
- DefaultUpdateRawMouse();
- }
-}
-
-void Window_DisableRawMouse(void) {
- DefaultDisableRawMouse();
- if (!grabCursor) return;
- XUngrabPointer(win_display, CurrentTime);
-}
-
-
+#include "Core.h"
/*########################################################################################################################*
*---------------------------------------------------Carbon/Cocoa window---------------------------------------------------*
*#########################################################################################################################*/
-#elif defined CC_BUILD_CARBON || defined CC_BUILD_COCOA
+#if defined CC_BUILD_CARBON || defined CC_BUILD_COCOA
#include "_WindowBase.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Errors.h"
#include
CC_OBJC_VISIBLE int windowX, windowY;
@@ -2631,1399 +659,14 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) {
#elif defined CC_BUILD_COCOA
/* NOTE: Mostly implemented in interop_cocoa.m */
#endif
-
-
-/*########################################################################################################################*
-*------------------------------------------------Emscripten canvas window-------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_WEB
-#include "_WindowBase.h"
-#include "Game.h"
-#include
-#include
-#include
-extern int interop_CanvasWidth(void);
-extern int interop_CanvasHeight(void);
-extern int interop_ScreenWidth(void);
-extern int interop_ScreenHeight(void);
-
-static cc_bool keyboardOpen, needResize;
-static int RawDpiScale(int x) { return (int)(x * emscripten_get_device_pixel_ratio()); }
-static int GetScreenWidth(void) { return RawDpiScale(interop_ScreenWidth()); }
-static int GetScreenHeight(void) { return RawDpiScale(interop_ScreenHeight()); }
-
-static void UpdateWindowBounds(void) {
- int width = interop_CanvasWidth();
- int height = interop_CanvasHeight();
- if (width == WindowInfo.Width && height == WindowInfo.Height) return;
-
- WindowInfo.Width = width;
- WindowInfo.Height = height;
- Event_RaiseVoid(&WindowEvents.Resized);
-}
-
-static void SetFullscreenBounds(void) {
- int width = GetScreenWidth();
- int height = GetScreenHeight();
- emscripten_set_canvas_element_size("#canvas", width, height);
-}
-
-/* Browser only allows pointer lock requests in response to user input */
-static void DeferredEnableRawMouse(void) {
- EmscriptenPointerlockChangeEvent status;
- if (!Input_RawMode) return;
-
- status.isActive = false;
- emscripten_get_pointerlock_status(&status);
- if (!status.isActive) emscripten_request_pointerlock("#canvas", false);
-}
-
-static EM_BOOL OnMouseWheel(int type, const EmscriptenWheelEvent* ev, void* data) {
- /* TODO: The scale factor isn't standardised.. is there a better way though? */
- Mouse_ScrollWheel(-Math_Sign(ev->deltaY));
- DeferredEnableRawMouse();
- return true;
-}
-
-static EM_BOOL OnMouseButton(int type, const EmscriptenMouseEvent* ev, void* data) {
- cc_bool down = type == EMSCRIPTEN_EVENT_MOUSEDOWN;
- /* https://stackoverflow.com/questions/60895686/how-to-get-mouse-buttons-4-5-browser-back-browser-forward-working-in-firef */
- switch (ev->button) {
- case 0: Input_Set(KEY_LMOUSE, down); break;
- case 1: Input_Set(KEY_MMOUSE, down); break;
- case 2: Input_Set(KEY_RMOUSE, down); break;
- case 3: Input_Set(KEY_XBUTTON1, down); break;
- case 4: Input_Set(KEY_XBUTTON2, down); break;
- }
-
- DeferredEnableRawMouse();
- return true;
-}
-
-/* input coordinates are CSS pixels, remap to internal pixels */
-static void RescaleXY(int* x, int* y) {
- double css_width, css_height;
- emscripten_get_element_css_size("#canvas", &css_width, &css_height);
-
- if (css_width && css_height) {
- *x = (int)(*x * WindowInfo.Width / css_width );
- *y = (int)(*y * WindowInfo.Height / css_height);
- } else {
- /* If css width or height is 0, something is bogus */
- /* Better to avoid divsision by 0 in that case though */
- }
-}
-
-static EM_BOOL OnMouseMove(int type, const EmscriptenMouseEvent* ev, void* data) {
- int x, y, buttons = ev->buttons;
- /* Set before position change, in case mouse buttons changed when outside window */
- Input_SetNonRepeatable(KEY_LMOUSE, buttons & 0x01);
- Input_SetNonRepeatable(KEY_RMOUSE, buttons & 0x02);
- Input_SetNonRepeatable(KEY_MMOUSE, buttons & 0x04);
-
- x = ev->targetX; y = ev->targetY;
- RescaleXY(&x, &y);
- Pointer_SetPosition(0, x, y);
- if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, ev->movementX, ev->movementY);
- return true;
-}
-
-/* TODO: Also query mouse coordinates globally (in OnMouseMove) and reuse interop_AdjustXY here */
-extern void interop_AdjustXY(int* x, int* y);
-
-static EM_BOOL OnTouchStart(int type, const EmscriptenTouchEvent* ev, void* data) {
- const EmscriptenTouchPoint* t;
- int i, x, y;
- for (i = 0; i < ev->numTouches; ++i) {
- t = &ev->touches[i];
- if (!t->isChanged) continue;
- x = t->targetX; y = t->targetY;
-
- interop_AdjustXY(&x, &y);
- RescaleXY(&x, &y);
- Input_AddTouch(t->identifier, x, y);
- }
- /* Don't intercept touchstart events while keyboard is open, that way */
- /* user can still touch to move the caret position in input textbox. */
- return !keyboardOpen;
-}
-
-static EM_BOOL OnTouchMove(int type, const EmscriptenTouchEvent* ev, void* data) {
- const EmscriptenTouchPoint* t;
- int i, x, y;
- for (i = 0; i < ev->numTouches; ++i) {
- t = &ev->touches[i];
- if (!t->isChanged) continue;
- x = t->targetX; y = t->targetY;
-
- interop_AdjustXY(&x, &y);
- RescaleXY(&x, &y);
- Input_UpdateTouch(t->identifier, x, y);
- }
- /* Don't intercept touchmove events while keyboard is open, that way */
- /* user can still touch to move the caret position in input textbox. */
- return !keyboardOpen;
-}
-
-static EM_BOOL OnTouchEnd(int type, const EmscriptenTouchEvent* ev, void* data) {
- const EmscriptenTouchPoint* t;
- int i, x, y;
- for (i = 0; i < ev->numTouches; ++i) {
- t = &ev->touches[i];
- if (!t->isChanged) continue;
- x = t->targetX; y = t->targetY;
-
- interop_AdjustXY(&x, &y);
- RescaleXY(&x, &y);
- Input_RemoveTouch(t->identifier, x, y);
- }
- /* Don't intercept touchend events while keyboard is open, that way */
- /* user can still touch to move the caret position in input textbox. */
- return !keyboardOpen;
-}
-
-static EM_BOOL OnFocus(int type, const EmscriptenFocusEvent* ev, void* data) {
- WindowInfo.Focused = type == EMSCRIPTEN_EVENT_FOCUS;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
- return true;
-}
-
-static EM_BOOL OnResize(int type, const EmscriptenUiEvent* ev, void *data) {
- UpdateWindowBounds(); needResize = true;
- return true;
-}
-/* This is only raised when going into fullscreen */
-static EM_BOOL OnCanvasResize(int type, const void* reserved, void *data) {
- UpdateWindowBounds(); needResize = true;
- return false;
-}
-static EM_BOOL OnFullscreenChange(int type, const EmscriptenFullscreenChangeEvent* ev, void *data) {
- UpdateWindowBounds(); needResize = true;
- return false;
-}
-
-static const char* OnBeforeUnload(int type, const void* ev, void *data) {
- if (!Game_ShouldClose()) {
- /* Exit pointer lock, otherwise when you press Ctrl+W, the */
- /* cursor remains invisible in the confirmation dialog */
- emscripten_exit_pointerlock();
- return "You have unsaved changes. Are you sure you want to quit?";
- }
- Window_Close();
- return NULL;
-}
-
-static int MapNativeKey(int k, int l) {
- if (k >= '0' && k <= '9') return k;
- if (k >= 'A' && k <= 'Z') return k;
- if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
- if (k >= DOM_VK_NUMPAD0 && k <= DOM_VK_NUMPAD9) { return KEY_KP0 + (k - DOM_VK_NUMPAD0); }
-
- switch (k) {
- case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
- case DOM_VK_TAB: return KEY_TAB;
- case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER;
- case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT;
- case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL;
- case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT;
- case DOM_VK_PAUSE: return KEY_PAUSE;
- case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
- case DOM_VK_ESCAPE: return KEY_ESCAPE;
- case DOM_VK_SPACE: return KEY_SPACE;
-
- case DOM_VK_PAGE_UP: return KEY_PAGEUP;
- case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
- case DOM_VK_END: return KEY_END;
- case DOM_VK_HOME: return KEY_HOME;
- case DOM_VK_LEFT: return KEY_LEFT;
- case DOM_VK_UP: return KEY_UP;
- case DOM_VK_RIGHT: return KEY_RIGHT;
- case DOM_VK_DOWN: return KEY_DOWN;
- case DOM_VK_PRINTSCREEN: return KEY_PRINTSCREEN;
- case DOM_VK_INSERT: return KEY_INSERT;
- case DOM_VK_DELETE: return KEY_DELETE;
-
- case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
- case DOM_VK_EQUALS: return KEY_EQUALS;
- case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN;
- case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
- case DOM_VK_ADD: return KEY_KP_PLUS;
- case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
- case DOM_VK_DECIMAL: return KEY_KP_DECIMAL;
- case DOM_VK_DIVIDE: return KEY_KP_DIVIDE;
- case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
- case DOM_VK_SCROLL_LOCK: return KEY_SCROLLLOCK;
-
- case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
- case DOM_VK_COMMA: return KEY_COMMA;
- case DOM_VK_PERIOD: return KEY_PERIOD;
- case DOM_VK_SLASH: return KEY_SLASH;
- case DOM_VK_BACK_QUOTE: return KEY_TILDE;
- case DOM_VK_OPEN_BRACKET: return KEY_LBRACKET;
- case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
- case DOM_VK_CLOSE_BRACKET: return KEY_RBRACKET;
- case DOM_VK_QUOTE: return KEY_QUOTE;
-
- /* chrome */
- case 186: return KEY_SEMICOLON;
- case 187: return KEY_EQUALS;
- case 189: return KEY_MINUS;
- }
- return KEY_NONE;
-}
-
-static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) {
- int key = MapNativeKey(ev->keyCode, ev->location);
- /* iOS safari still sends backspace key events, don't intercept those */
- if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false;
-
- if (key) Input_SetPressed(key);
- DeferredEnableRawMouse();
- if (!key) return false;
-
- /* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
- /* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
- /* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */
- if (Key_IsAltPressed()) return true;
- if (Key_IsWinPressed()) return key != 'C' && key != 'V';
- if (Key_IsCtrlPressed()) return key != 'C' && key != 'V';
-
- /* Space needs special handling, as intercepting this prevents the ' ' key press event */
- /* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
- if (key == KEY_SPACE) return !keyboardOpen;
-
- /* Must not intercept KeyDown for regular keys, otherwise KeyPress doesn't get raised. */
- /* However, do want to prevent browser's behaviour on F11, F5, home etc. */
- /* e.g. not preventing F11 means browser makes page fullscreen instead of just canvas */
- return (key >= KEY_F1 && key <= KEY_F24) || (key >= KEY_UP && key <= KEY_RIGHT) ||
- (key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
-}
-
-static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) {
- int key = MapNativeKey(ev->keyCode, ev->location);
- if (key) Input_SetReleased(key);
- DeferredEnableRawMouse();
- return key != KEY_NONE;
-}
-
-static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
- char keyChar;
- DeferredEnableRawMouse();
- /* When on-screen keyboard is open, we don't want to intercept any key presses, */
- /* because they should be sent to the HTML text input instead. */
- /* (Chrome for android sends keypresses sometimes for '0' to '9' keys) */
- /* - If any keys are intercepted, this causes the HTML text input to become */
- /* desynchronised from the chat/menu input widget the user sees in game. */
- /* - This causes problems such as attempting to backspace all text later to */
- /* not actually backspace everything. (because the HTML text input does not */
- /* have these intercepted key presses in its text buffer) */
- if (Input_TouchMode && keyboardOpen) return false;
-
- /* Safari on macOS still sends a keypress event, which must not be cancelled */
- /* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */
- if (ev->metaKey) return false;
-
- if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
- Event_RaiseInt(&InputEvents.Press, keyChar);
- }
- return true;
-}
-
-/* Really old emscripten versions (e.g. 1.38.21) don't have this defined */
-/* Can't just use "#window", newer versions switched to const int instead */
-#ifndef EMSCRIPTEN_EVENT_TARGET_WINDOW
-#define EMSCRIPTEN_EVENT_TARGET_WINDOW "#window"
#endif
-static void HookEvents(void) {
- emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnMouseWheel);
- emscripten_set_mousedown_callback("#canvas", NULL, 0, OnMouseButton);
- emscripten_set_mouseup_callback("#canvas", NULL, 0, OnMouseButton);
- emscripten_set_mousemove_callback("#canvas", NULL, 0, OnMouseMove);
- emscripten_set_fullscreenchange_callback("#canvas", NULL, 0, OnFullscreenChange);
-
- emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
- emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize);
- emscripten_set_beforeunload_callback( NULL, OnBeforeUnload);
-
- emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown);
- emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp);
- emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress);
-
- emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart);
- emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchMove);
- emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
- emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
-}
-
-static void UnhookEvents(void) {
- emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_mousedown_callback("#canvas", NULL, 0, NULL);
- emscripten_set_mouseup_callback("#canvas", NULL, 0, NULL);
- emscripten_set_mousemove_callback("#canvas", NULL, 0, NULL);
-
- emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_beforeunload_callback( NULL, NULL);
-
- emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
-
- emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
- emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
-}
-
-extern int interop_IsAndroid(void);
-extern int interop_IsIOS(void);
-extern void interop_AddClipboardListeners(void);
-extern void interop_ForceTouchPageLayout(void);
-void Window_Init(void) {
- int is_ios, droid;
- DisplayInfo.Width = GetScreenWidth();
- DisplayInfo.Height = GetScreenHeight();
- DisplayInfo.Depth = 24;
-
- DisplayInfo.ScaleX = emscripten_get_device_pixel_ratio();
- DisplayInfo.ScaleY = DisplayInfo.ScaleX;
- interop_AddClipboardListeners();
-
- droid = interop_IsAndroid();
- is_ios = interop_IsIOS();
- Input_SetTouchMode(is_ios || droid);
-
- /* iOS shifts the whole webpage up when opening chat, which causes problems */
- /* as the chat/send butons are positioned at the top of the canvas - they */
- /* get pushed offscreen and can't be used at all anymore. So handle this */
- /* case specially by positioning them at the bottom instead for iOS. */
- WindowInfo.SoftKeyboard = is_ios ? SOFT_KEYBOARD_SHIFT : SOFT_KEYBOARD_RESIZE;
-
- /* Let the webpage know it needs to force a mobile layout */
- if (!Input_TouchMode) return;
- interop_ForceTouchPageLayout();
-}
-
-extern void interop_InitContainer(void);
-void Window_Create(int width, int height) {
- WindowInfo.Exists = true;
- WindowInfo.Focused = true;
- HookEvents();
- /* Let the webpage decide on initial bounds */
- WindowInfo.Width = interop_CanvasWidth();
- WindowInfo.Height = interop_CanvasHeight();
- interop_InitContainer();
-}
-
-extern void interop_SetPageTitle(const char* title);
-void Window_SetTitle(const cc_string* title) {
- char str[NATIVE_STR_LEN];
- Platform_EncodeUtf8(str, title);
- interop_SetPageTitle(str);
-}
-
-static char pasteBuffer[512];
-static cc_string pasteStr;
-EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) {
- Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
-}
-
-EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) {
- String_InitArray(pasteStr, pasteBuffer);
- String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048));
-}
-
-EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
- Window_StoreClipboardText(src);
- Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
-}
-
-extern void interop_TryGetClipboardText(void);
-void Clipboard_GetText(cc_string* value) {
- /* Window_StoreClipboardText may or may not be called by this */
- interop_TryGetClipboardText();
-
- String_Copy(value, &pasteStr);
- pasteStr.length = 0;
-}
-
-extern void interop_TrySetClipboardText(const char* text);
-void Clipboard_SetText(const cc_string* value) {
- char str[NATIVE_STR_LEN];
- Platform_EncodeUtf8(str, value);
- interop_TrySetClipboardText(str);
-}
-
-void Window_Show(void) { }
-
-int Window_GetWindowState(void) {
- EmscriptenFullscreenChangeEvent status = { 0 };
- emscripten_get_fullscreen_status(&status);
- return status.isFullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL;
-}
-
-extern int interop_GetContainerID(void);
-extern void interop_EnterFullscreen(void);
-cc_result Window_EnterFullscreen(void) {
- EmscriptenFullscreenStrategy strategy;
- const char* target;
- int res;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
-
- strategy.canvasResizedCallback = OnCanvasResize;
- strategy.canvasResizedCallbackUserData = NULL;
-
- /* TODO: Return container element ID instead of hardcoding here */
- res = interop_GetContainerID();
- target = res ? "canvas_wrapper" : "#canvas";
-
- res = emscripten_request_fullscreen_strategy(target, 1, &strategy);
- if (res == EMSCRIPTEN_RESULT_NOT_SUPPORTED) res = ERR_NOT_SUPPORTED;
- if (res) return res;
-
- interop_EnterFullscreen();
- return 0;
-}
-
-cc_result Window_ExitFullscreen(void) {
- emscripten_exit_fullscreen();
- UpdateWindowBounds();
- return 0;
-}
-
-void Window_SetSize(int width, int height) {
- emscripten_set_canvas_element_size("#canvas", width, height);
- /* CSS size is in CSS units not pixel units */
- emscripten_set_element_css_size("#canvas", width / DisplayInfo.ScaleX, height / DisplayInfo.ScaleY);
- UpdateWindowBounds();
-}
-
-void Window_Close(void) {
- WindowInfo.Exists = false;
- Event_RaiseVoid(&WindowEvents.Closing);
- /* If the game is closed while in fullscreen, the last rendered frame stays */
- /* shown in fullscreen, but the game can't be interacted with anymore */
- Window_ExitFullscreen();
-
- /* Don't want cursor stuck on the dead 0,0 canvas */
- Window_DisableRawMouse();
- Window_SetSize(0, 0);
- UnhookEvents();
-}
-
-extern void interop_RequestCanvasResize(void);
-void Window_ProcessEvents(void) {
- if (!needResize) return;
- needResize = false;
- if (!WindowInfo.Exists) return;
-
- if (Window_GetWindowState() == WINDOW_STATE_FULLSCREEN) {
- SetFullscreenBounds();
- } else {
- /* Webpage can adjust canvas size if it wants to */
- interop_RequestCanvasResize();
- }
- UpdateWindowBounds();
-}
-
-/* Not needed because browser provides relative mouse and touch events */
-static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
-/* Not allowed to move cursor from javascript */
-void Cursor_SetPosition(int x, int y) { }
-
-extern void interop_SetCursorVisible(int visible);
-static void Cursor_DoSetVisible(cc_bool visible) {
- interop_SetCursorVisible(visible);
-}
-
-extern void interop_ShowDialog(const char* title, const char* msg);
-static void ShowDialogCore(const char* title, const char* msg) {
- interop_ShowDialog(title, msg);
-}
-
-static OpenFileDialogCallback uploadCallback;
-EMSCRIPTEN_KEEPALIVE void Window_OnFileUploaded(const char* src) {
- cc_string file; char buffer[FILENAME_SIZE];
- String_InitArray(file, buffer);
-
- String_AppendUtf8(&file, src, String_Length(src));
- uploadCallback(&file);
- uploadCallback = NULL;
-}
-
-extern void interop_OpenFileDialog(const char* filter);
-cc_result Window_OpenFileDialog(const char* filter, OpenFileDialogCallback callback) {
- uploadCallback = callback;
- /* Calls Window_OnFileUploaded on success */
- interop_OpenFileDialog(filter);
- return ERR_NOT_SUPPORTED;
-}
-
-void Window_AllocFramebuffer(struct Bitmap* bmp) { }
-void Window_DrawFramebuffer(Rect2D r) { }
-void Window_FreeFramebuffer(struct Bitmap* bmp) { }
-
-extern void interop_OpenKeyboard(const char* text, int type, const char* placeholder);
-extern void interop_SetKeyboardText(const char* text);
-extern void interop_CloseKeyboard(void);
-
-EMSCRIPTEN_KEEPALIVE void Window_OnTextChanged(const char* src) {
- cc_string str; char buffer[800];
- String_InitArray(str, buffer);
-
- String_AppendUtf8(&str, src, String_CalcLen(src, 3200));
- Event_RaiseString(&InputEvents.TextChanged, &str);
-}
-
-void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
- char str[NATIVE_STR_LEN];
- keyboardOpen = true;
- if (!Input_TouchMode) return;
-
- Platform_EncodeUtf8(str, args->text);
- Platform_LogConst("OPEN SESAME");
- interop_OpenKeyboard(str, args->type, args->placeholder);
-}
-
-void Window_SetKeyboardText(const cc_string* text) {
- char str[NATIVE_STR_LEN];
- if (!Input_TouchMode) return;
-
- Platform_EncodeUtf8(str, text);
- interop_SetKeyboardText(str);
-}
-
-void Window_CloseKeyboard(void) {
- keyboardOpen = false;
- if (!Input_TouchMode) return;
- interop_CloseKeyboard();
-}
-
-void Window_EnableRawMouse(void) {
- RegrabMouse();
- /* defer pointerlock request until next user input */
- Input_RawMode = true;
-}
-void Window_UpdateRawMouse(void) { }
-
-void Window_DisableRawMouse(void) {
- RegrabMouse();
- emscripten_exit_pointerlock();
- Input_RawMode = false;
-}
-
-
-/*########################################################################################################################*
-*------------------------------------------------Android activity window-------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_ANDROID
-#include "_WindowBase.h"
-#include
-#include
-#include
-static ANativeWindow* win_handle;
-
-static void RefreshWindowBounds(void) {
- WindowInfo.Width = ANativeWindow_getWidth(win_handle);
- WindowInfo.Height = ANativeWindow_getHeight(win_handle);
- Platform_Log2("SCREEN BOUNDS: %i,%i", &WindowInfo.Width, &WindowInfo.Height);
- Event_RaiseVoid(&WindowEvents.Resized);
-}
-
-static int MapNativeKey(int code) {
- if (code >= AKEYCODE_0 && code <= AKEYCODE_9) return (code - AKEYCODE_0) + '0';
- if (code >= AKEYCODE_A && code <= AKEYCODE_Z) return (code - AKEYCODE_A) + 'A';
- if (code >= AKEYCODE_F1 && code <= AKEYCODE_F12) return (code - AKEYCODE_F1) + KEY_F1;
- if (code >= AKEYCODE_NUMPAD_0 && code <= AKEYCODE_NUMPAD_9) return (code - AKEYCODE_NUMPAD_0) + KEY_KP0;
-
- switch (code) {
- /* TODO: AKEYCODE_STAR */
- /* TODO: AKEYCODE_POUND */
- case AKEYCODE_BACK: return KEY_ESCAPE;
- case AKEYCODE_COMMA: return KEY_COMMA;
- case AKEYCODE_PERIOD: return KEY_PERIOD;
- case AKEYCODE_ALT_LEFT: return KEY_LALT;
- case AKEYCODE_ALT_RIGHT: return KEY_RALT;
- case AKEYCODE_SHIFT_LEFT: return KEY_LSHIFT;
- case AKEYCODE_SHIFT_RIGHT: return KEY_RSHIFT;
- case AKEYCODE_TAB: return KEY_TAB;
- case AKEYCODE_SPACE: return KEY_SPACE;
- case AKEYCODE_ENTER: return KEY_ENTER;
- case AKEYCODE_DEL: return KEY_BACKSPACE;
- case AKEYCODE_GRAVE: return KEY_TILDE;
- case AKEYCODE_MINUS: return KEY_MINUS;
- case AKEYCODE_EQUALS: return KEY_EQUALS;
- case AKEYCODE_LEFT_BRACKET: return KEY_LBRACKET;
- case AKEYCODE_RIGHT_BRACKET: return KEY_RBRACKET;
- case AKEYCODE_BACKSLASH: return KEY_BACKSLASH;
- case AKEYCODE_SEMICOLON: return KEY_SEMICOLON;
- case AKEYCODE_APOSTROPHE: return KEY_QUOTE;
- case AKEYCODE_SLASH: return KEY_SLASH;
- /* TODO: AKEYCODE_AT */
- /* TODO: AKEYCODE_PLUS */
- /* TODO: AKEYCODE_MENU */
- case AKEYCODE_PAGE_UP: return KEY_PAGEUP;
- case AKEYCODE_PAGE_DOWN: return KEY_PAGEDOWN;
- case AKEYCODE_ESCAPE: return KEY_ESCAPE;
- case AKEYCODE_FORWARD_DEL: return KEY_DELETE;
- case AKEYCODE_CTRL_LEFT: return KEY_LCTRL;
- case AKEYCODE_CTRL_RIGHT: return KEY_RCTRL;
- case AKEYCODE_CAPS_LOCK: return KEY_CAPSLOCK;
- case AKEYCODE_SCROLL_LOCK: return KEY_SCROLLLOCK;
- case AKEYCODE_META_LEFT: return KEY_LWIN;
- case AKEYCODE_META_RIGHT: return KEY_RWIN;
- case AKEYCODE_SYSRQ: return KEY_PRINTSCREEN;
- case AKEYCODE_BREAK: return KEY_PAUSE;
- case AKEYCODE_INSERT: return KEY_INSERT;
- case AKEYCODE_NUM_LOCK: return KEY_NUMLOCK;
- case AKEYCODE_NUMPAD_DIVIDE: return KEY_KP_DIVIDE;
- case AKEYCODE_NUMPAD_MULTIPLY: return KEY_KP_MULTIPLY;
- case AKEYCODE_NUMPAD_SUBTRACT: return KEY_KP_MINUS;
- case AKEYCODE_NUMPAD_ADD: return KEY_KP_PLUS;
- case AKEYCODE_NUMPAD_DOT: return KEY_KP_DECIMAL;
- case AKEYCODE_NUMPAD_ENTER: return KEY_KP_ENTER;
- }
- return KEY_NONE;
-}
-
-static void JNICALL java_processKeyDown(JNIEnv* env, jobject o, jint code) {
- int key = MapNativeKey(code);
- Platform_Log2("KEY - DOWN %i,%i", &code, &key);
- if (key) Input_SetPressed(key);
-}
-
-static void JNICALL java_processKeyUp(JNIEnv* env, jobject o, jint code) {
- int key = MapNativeKey(code);
- Platform_Log2("KEY - UP %i,%i", &code, &key);
- if (key) Input_SetReleased(key);
-}
-
-static void JNICALL java_processKeyChar(JNIEnv* env, jobject o, jint code) {
- char keyChar;
- int key = MapNativeKey(code);
- Platform_Log2("KEY - PRESS %i,%i", &code, &key);
-
- if (Convert_TryCodepointToCP437(code, &keyChar)) {
- Event_RaiseInt(&InputEvents.Press, keyChar);
- }
-}
-
-static void JNICALL java_processKeyText(JNIEnv* env, jobject o, jstring str) {
- char buffer[NATIVE_STR_LEN];
- cc_string text = JavaGetString(env, str, buffer);
- Platform_Log1("KEY - TEXT %s", &text);
- Event_RaiseString(&InputEvents.TextChanged, &text);
-}
-
-static void JNICALL java_processPointerDown(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
- Platform_Log4("POINTER %i (%i) - DOWN %i,%i", &id, &isMouse, &x, &y);
- Input_AddTouch(id, x, y);
-}
-
-static void JNICALL java_processPointerUp(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
- Platform_Log4("POINTER %i (%i) - UP %i,%i", &id, &isMouse, &x, &y);
- Input_RemoveTouch(id, x, y);
-}
-
-static void JNICALL java_processPointerMove(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
- Platform_Log4("POINTER %i (%i) - MOVE %i,%i", &id, &isMouse, &x, &y);
- Input_UpdateTouch(id, x, y);
-}
-
-static void JNICALL java_processSurfaceCreated(JNIEnv* env, jobject o, jobject surface) {
- Platform_LogConst("WIN - CREATED");
- win_handle = ANativeWindow_fromSurface(env, surface);
- WindowInfo.Handle = win_handle;
- RefreshWindowBounds();
- /* TODO: Restore context */
- Event_RaiseVoid(&WindowEvents.Created);
-}
-
-#include "Graphics.h"
-static void JNICALL java_processSurfaceDestroyed(JNIEnv* env, jobject o) {
- Platform_LogConst("WIN - DESTROYED");
- if (win_handle) ANativeWindow_release(win_handle);
-
- win_handle = NULL;
- WindowInfo.Handle = NULL;
- /* eglSwapBuffers might return EGL_BAD_SURFACE, EGL_BAD_ALLOC, or some other error */
- /* Instead the context is lost here in a consistent manner */
- if (Gfx.Created) Gfx_LoseContext("surface lost");
- JavaCallVoid(env, "processedSurfaceDestroyed", "()V", NULL);
-}
-
-static void JNICALL java_processSurfaceResized(JNIEnv* env, jobject o, jobject surface) {
- Platform_LogConst("WIN - RESIZED");
- RefreshWindowBounds();
-}
-
-static void JNICALL java_processSurfaceRedrawNeeded(JNIEnv* env, jobject o) {
- Platform_LogConst("WIN - REDRAW");
- Event_RaiseVoid(&WindowEvents.Redraw);
-}
-
-static void JNICALL java_onStart(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - ON START");
-}
-
-static void JNICALL java_onStop(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - ON STOP");
-}
-
-static void JNICALL java_onResume(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - ON RESUME");
- /* TODO: Resume rendering */
-}
-
-static void JNICALL java_onPause(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - ON PAUSE");
- /* TODO: Disable rendering */
-}
-
-static void JNICALL java_onDestroy(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - ON DESTROY");
-
- if (WindowInfo.Exists) Window_Close();
- /* TODO: signal to java code we're done */
- JavaCallVoid(env, "processedDestroyed", "()V", NULL);
-}
-
-static void JNICALL java_onGotFocus(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - GOT FOCUS");
- WindowInfo.Focused = true;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
-}
-
-static void JNICALL java_onLostFocus(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - LOST FOCUS");
- WindowInfo.Focused = false;
- Event_RaiseVoid(&WindowEvents.FocusChanged);
- /* TODO: Disable rendering? */
-}
-
-static void JNICALL java_onLowMemory(JNIEnv* env, jobject o) {
- Platform_LogConst("APP - LOW MEM");
- /* TODO: Low memory */
-}
-
-static const JNINativeMethod methods[19] = {
- { "processKeyDown", "(I)V", java_processKeyDown },
- { "processKeyUp", "(I)V", java_processKeyUp },
- { "processKeyChar", "(I)V", java_processKeyChar },
- { "processKeyText", "(Ljava/lang/String;)V", java_processKeyText },
-
- { "processPointerDown", "(IIII)V", java_processPointerDown },
- { "processPointerUp", "(IIII)V", java_processPointerUp },
- { "processPointerMove", "(IIII)V", java_processPointerMove },
-
- { "processSurfaceCreated", "(Landroid/view/Surface;)V", java_processSurfaceCreated },
- { "processSurfaceDestroyed", "()V", java_processSurfaceDestroyed },
- { "processSurfaceResized", "(Landroid/view/Surface;)V", java_processSurfaceResized },
- { "processSurfaceRedrawNeeded", "()V", java_processSurfaceRedrawNeeded },
-
- { "processOnStart", "()V", java_onStart },
- { "processOnStop", "()V", java_onStop },
- { "processOnResume", "()V", java_onResume },
- { "processOnPause", "()V", java_onPause },
- { "processOnDestroy", "()V", java_onDestroy },
-
- { "processOnGotFocus", "()V", java_onGotFocus },
- { "processOnLostFocus", "()V", java_onLostFocus },
- { "processOnLowMemory", "()V", java_onLowMemory }
-};
-
-void Window_Init(void) {
- JNIEnv* env;
- /* TODO: ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN, 0); */
- JavaGetCurrentEnv(env);
- JavaRegisterNatives(env, methods);
-
- WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
- Input_SetTouchMode(true);
-
- DisplayInfo.Depth = 32;
- DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL);
- DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL);
-}
-
-void Window_Create(int width, int height) {
- WindowInfo.Exists = true;
- /* actual window creation is done when processSurfaceCreated is received */
-}
-
-static cc_bool winCreated;
-static void OnWindowCreated(void* obj) { winCreated = true; }
-cc_bool Window_RemakeSurface(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- winCreated = false;
-
- /* Force window to be destroyed and re-created */
- /* (see comments in setupForGame for why this has to be done) */
- JavaCallVoid(env, "setupForGame", "()V", NULL);
- Event_Register_(&WindowEvents.Created, NULL, OnWindowCreated);
- Platform_LogConst("Entering wait for window exist loop..");
-
- /* Loop until window gets created async */
- while (WindowInfo.Exists && !winCreated) {
- Window_ProcessEvents();
- Thread_Sleep(10);
- }
-
- Platform_LogConst("OK window created..");
- Event_Unregister_(&WindowEvents.Created, NULL, OnWindowCreated);
- return winCreated;
-}
-
-void Window_SetTitle(const cc_string* title) {
- /* TODO: Implement this somehow */
- /* Maybe https://stackoverflow.com/questions/2198410/how-to-change-title-of-activity-in-android */
-}
-
-void Clipboard_GetText(cc_string* value) {
- JavaCall_Void_String("getClipboardText", value);
-}
-void Clipboard_SetText(const cc_string* value) {
- JavaCall_String_Void("setClipboardText", value);
-}
-
-void Window_Show(void) { } /* Window already visible */
-int Window_GetWindowState(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- return JavaCallInt(env, "getWindowState", "()I", NULL);
-}
-
-cc_result Window_EnterFullscreen(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- JavaCallVoid(env, "enterFullscreen", "()V", NULL);
- return 0;
-}
-
-cc_result Window_ExitFullscreen(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- JavaCallVoid(env, "exitFullscreen", "()V", NULL);
- return 0;
-}
-
-void Window_SetSize(int width, int height) { }
-
-void Window_Close(void) {
- WindowInfo.Exists = false;
- Event_RaiseVoid(&WindowEvents.Closing);
- /* TODO: Do we need to call finish here */
- /* ANativeActivity_finish(app->activity); */
-}
-
-void Window_ProcessEvents(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- /* TODO: Cache the java env and cache the method ID!!!!! */
- JavaCallVoid(env, "processEvents", "()V", NULL);
-}
-
-/* No actual mouse cursor */
-static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
-void Cursor_SetPosition(int x, int y) { }
-static void Cursor_DoSetVisible(cc_bool visible) { }
-
-static void ShowDialogCore(const char* title, const char* msg) {
- JNIEnv* env;
- jvalue args[2];
- JavaGetCurrentEnv(env);
-
- Platform_LogConst(title);
- Platform_LogConst(msg);
- /* in case surface destroyed message has arrived */
- Window_ProcessEvents();
-
- args[0].l = JavaMakeConst(env, title);
- args[1].l = JavaMakeConst(env, msg);
- JavaCallVoid(env, "showAlert", "(Ljava/lang/String;Ljava/lang/String;)V", args);
- (*env)->DeleteLocalRef(env, args[0].l);
- (*env)->DeleteLocalRef(env, args[1].l);
-}
-
-static struct Bitmap fb_bmp;
-void Window_AllocFramebuffer(struct Bitmap* bmp) {
- bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
- fb_bmp = *bmp;
-}
-
-void Window_DrawFramebuffer(Rect2D r) {
- ANativeWindow_Buffer buffer;
- cc_uint32* src;
- cc_uint32* dst;
- ARect b;
- int y, res, size;
-
- /* window not created yet */
- if (!win_handle) return;
- b.left = r.X; b.right = r.X + r.Width;
- b.top = r.Y; b.bottom = r.Y + r.Height;
-
- /* Platform_Log4("DIRTY: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
- res = ANativeWindow_lock(win_handle, &buffer, &b);
- if (res) Logger_Abort2(res, "Locking window pixels");
- /* Platform_Log4("ADJUS: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
-
- /* In some rare cases, the returned locked region will be entire area of the surface */
- /* This can cause a crash if the surface has been resized (i.e. device rotated), */
- /* but the framebuffer has not been resized yet. So always constrain bounds. */
- b.left = min(b.left, fb_bmp.width); b.right = min(b.right, fb_bmp.width);
- b.top = min(b.top, fb_bmp.height); b.bottom = min(b.bottom, fb_bmp.height);
-
- src = (cc_uint32*)fb_bmp.scan0 + b.left;
- dst = (cc_uint32*)buffer.bits + b.left;
- size = (b.right - b.left) * 4;
-
- for (y = b.top; y < b.bottom; y++) {
- Mem_Copy(dst + y * buffer.stride, src + y * fb_bmp.width, size);
- }
- res = ANativeWindow_unlockAndPost(win_handle);
- if (res) Logger_Abort2(res, "Unlocking window pixels");
-}
-
-void Window_FreeFramebuffer(struct Bitmap* bmp) {
- Mem_Free(bmp->scan0);
-}
-
-void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) {
- JNIEnv* env;
- jvalue args[2];
- JavaGetCurrentEnv(env);
-
- args[0].l = JavaMakeString(env, kArgs->text);
- args[1].i = kArgs->type;
- JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
- (*env)->DeleteLocalRef(env, args[0].l);
-}
-
-void Window_SetKeyboardText(const cc_string* text) {
- JNIEnv* env;
- jvalue args[1];
- JavaGetCurrentEnv(env);
-
- args[0].l = JavaMakeString(env, text);
- JavaCallVoid(env, "setKeyboardText", "(Ljava/lang/String;)V", args);
- (*env)->DeleteLocalRef(env, args[0].l);
-}
-
-void Window_CloseKeyboard(void) {
- JNIEnv* env;
- JavaGetCurrentEnv(env);
- JavaCallVoid(env, "closeKeyboard", "()V", NULL);
-}
-
-void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); }
-void Window_UpdateRawMouse(void) { }
-void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
-#endif
-
-
#ifdef CC_BUILD_GL
-/* OpenGL contexts are heavily tied to the window, so for simplicitly are also included here */
-/* SDL and EGL are platform agnostic, other OpenGL context backends are tied to one windowing system. */
-#define GLContext_IsInvalidAddress(ptr) (ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr == (void*)2)
-
-void GLContext_GetAll(const struct DynamicLibSym* syms, int count) {
- int i;
- for (i = 0; i < count; i++) {
- *syms[i].symAddr = GLContext_GetAddress(syms[i].name);
- }
-}
-
-/*########################################################################################################################*
-*-------------------------------------------------------SDL OpenGL--------------------------------------------------------*
-*#########################################################################################################################*/
-#if defined CC_BUILD_SDL
-static SDL_GLContext win_ctx;
-
-void GLContext_Create(void) {
- struct GraphicsMode mode;
- InitGraphicsMode(&mode);
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, mode.R);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, mode.B);
- SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A);
-
- SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH);
- SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
- SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
-
- win_ctx = SDL_GL_CreateContext(win_handle);
- if (!win_ctx) Window_SDLFail("creating OpenGL context");
-}
-
-void GLContext_Update(void) { }
-cc_bool GLContext_TryRestore(void) { return true; }
-void GLContext_Free(void) {
- SDL_GL_DeleteContext(win_ctx);
- win_ctx = NULL;
-}
-
-void* GLContext_GetAddress(const char* function) {
- return SDL_GL_GetProcAddress(function);
-}
-
-cc_bool GLContext_SwapBuffers(void) {
- SDL_GL_SwapWindow(win_handle);
- return true;
-}
-
-void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
- SDL_GL_SetSwapInterval(vsync);
-}
-void GLContext_GetApiInfo(cc_string* info) { }
-
-
-/*########################################################################################################################*
-*-------------------------------------------------------EGL OpenGL--------------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_EGL
-#include
-static EGLDisplay ctx_display;
-static EGLContext ctx_context;
-static EGLSurface ctx_surface;
-static EGLConfig ctx_config;
-static EGLint ctx_numConfig;
-
-#ifdef CC_BUILD_X11
-static XVisualInfo GLContext_SelectVisual(void) {
- XVisualInfo info;
- cc_result res;
- int screen = DefaultScreen(win_display);
-
- res = XMatchVisualInfo(win_display, screen, 24, TrueColor, &info) ||
- XMatchVisualInfo(win_display, screen, 32, TrueColor, &info);
-
- if (!res) Logger_Abort("Selecting visual");
- return info;
-}
-#endif
-
-static void GLContext_InitSurface(void) {
- if (!win_handle) return; /* window not created or lost */
- ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, win_handle, NULL);
-
- if (!ctx_surface) return;
- eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
-}
-
-static void GLContext_FreeSurface(void) {
- if (!ctx_surface) return;
- eglMakeCurrent(ctx_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroySurface(ctx_display, ctx_surface);
- ctx_surface = NULL;
-}
-
-void GLContext_Create(void) {
- static EGLint contextAttribs[3] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
- static EGLint attribs[19] = {
- EGL_RED_SIZE, 0, EGL_GREEN_SIZE, 0,
- EGL_BLUE_SIZE, 0, EGL_ALPHA_SIZE, 0,
- EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH,
- EGL_STENCIL_SIZE, 0,
- EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
- EGL_NONE
- };
-
- struct GraphicsMode mode;
- InitGraphicsMode(&mode);
- attribs[1] = mode.R; attribs[3] = mode.G;
- attribs[5] = mode.B; attribs[7] = mode.A;
-
- ctx_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- eglInitialize(ctx_display, NULL, NULL);
- eglBindAPI(EGL_OPENGL_ES_API);
- eglChooseConfig(ctx_display, attribs, &ctx_config, 1, &ctx_numConfig);
-
- ctx_context = eglCreateContext(ctx_display, ctx_config, EGL_NO_CONTEXT, contextAttribs);
- GLContext_InitSurface();
-}
-
-void GLContext_Update(void) {
- GLContext_FreeSurface();
- GLContext_InitSurface();
-}
-
-cc_bool GLContext_TryRestore(void) {
- GLContext_FreeSurface();
- GLContext_InitSurface();
- return ctx_surface != NULL;
-}
-
-void GLContext_Free(void) {
- GLContext_FreeSurface();
- eglDestroyContext(ctx_display, ctx_context);
- eglTerminate(ctx_display);
-}
-
-void* GLContext_GetAddress(const char* function) {
- return eglGetProcAddress(function);
-}
-
-cc_bool GLContext_SwapBuffers(void) {
- EGLint err;
- if (!ctx_surface) return false;
- if (eglSwapBuffers(ctx_display, ctx_surface)) return true;
-
- err = eglGetError();
- /* TODO: figure out what errors need to be handled here */
- Logger_Abort2(err, "Failed to swap buffers");
- return false;
-}
-
-void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
- eglSwapInterval(ctx_display, vsync);
-}
-void GLContext_GetApiInfo(cc_string* info) { }
-
-
-/*########################################################################################################################*
-*-------------------------------------------------------WGL OpenGL--------------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_WINGUI
-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 };
- 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 = mode->IsIndexed ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
- pfd.cRedBits = mode->R;
- pfd.cGreenBits = mode->G;
- pfd.cBlueBits = mode->B;
- pfd.cAlphaBits = mode->A;
-
- int 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;
-
- 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) 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;
-}
-
-void* GLContext_GetAddress(const char* function) {
- void* addr = (void*)wglGetProcAddress(function);
- return GLContext_IsInvalidAddress(addr) ? NULL : addr;
-}
-
-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) { }
-
-
-/*########################################################################################################################*
-*-------------------------------------------------------glX OpenGL--------------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_X11
-#include
-static GLXContext ctx_handle;
-typedef int (*FP_SWAPINTERVAL)(int interval);
-typedef Bool (*FP_QUERYRENDERER)(int attribute, unsigned int* value);
-static FP_SWAPINTERVAL swapIntervalMESA, swapIntervalSGI;
-static FP_QUERYRENDERER queryRendererMESA;
-
-void GLContext_Create(void) {
- static const cc_string vsync_mesa = String_FromConst("GLX_MESA_swap_control");
- static const cc_string vsync_sgi = String_FromConst("GLX_SGI_swap_control");
- static const cc_string info_mesa = String_FromConst("GLX_MESA_query_renderer");
-
- const char* raw_exts;
- cc_string exts;
- ctx_handle = glXCreateContext(win_display, &win_visual, NULL, true);
-
- if (!ctx_handle) {
- Platform_LogConst("Context create failed. Trying indirect...");
- ctx_handle = glXCreateContext(win_display, &win_visual, NULL, false);
- }
- if (!ctx_handle) Logger_Abort("Failed to create OpenGL context");
-
- if (!glXIsDirect(win_display, ctx_handle)) {
- Platform_LogConst("== WARNING: Context is not direct ==");
- }
- if (!glXMakeCurrent(win_display, win_handle, ctx_handle)) {
- Logger_Abort("Failed to make OpenGL context current.");
- }
-
- /* GLX may return non-null function pointers that don't actually work */
- /* So we need to manually check the extensions string for support */
- raw_exts = glXQueryExtensionsString(win_display, DefaultScreen(win_display));
- exts = String_FromReadonly(raw_exts);
-
- if (String_CaselessContains(&exts, &vsync_mesa)) {
- swapIntervalMESA = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalMESA");
- }
- if (String_CaselessContains(&exts, &vsync_sgi)) {
- swapIntervalSGI = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalSGI");
- }
- if (String_CaselessContains(&exts, &info_mesa)) {
- queryRendererMESA = (FP_QUERYRENDERER)GLContext_GetAddress("glXQueryCurrentRendererIntegerMESA");
- }
-}
-
-void GLContext_Update(void) { }
-cc_bool GLContext_TryRestore(void) { return true; }
-void GLContext_Free(void) {
- if (!ctx_handle) return;
- glXMakeCurrent(win_display, None, NULL);
- glXDestroyContext(win_display, ctx_handle);
- ctx_handle = NULL;
-}
-
-void* GLContext_GetAddress(const char* function) {
- void* addr = (void*)glXGetProcAddress((const GLubyte*)function);
- return GLContext_IsInvalidAddress(addr) ? NULL : addr;
-}
-
-cc_bool GLContext_SwapBuffers(void) {
- glXSwapBuffers(win_display, win_handle);
- return true;
-}
-
-void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
- int res = 0;
- if (swapIntervalMESA) {
- res = swapIntervalMESA(vsync);
- } else if (swapIntervalSGI) {
- res = swapIntervalSGI(vsync);
- }
- if (res) Platform_Log1("Set VSync failed, error: %i", &res);
-}
-
-void GLContext_GetApiInfo(cc_string* info) {
- unsigned int vram, acc;
- if (!queryRendererMESA) return;
-
- queryRendererMESA(0x8186, &acc);
- queryRendererMESA(0x8187, &vram);
- String_Format2(info, "VRAM: %i MB, %c", &vram,
- acc ? "HW accelerated" : "no HW acceleration");
-}
-
-static void GetAttribs(struct GraphicsMode* mode, int* attribs, int depth) {
- int i = 0;
- /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.opengl/doc/openglrf/glXChooseFBConfig.htm%23glxchoosefbconfig */
- /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.opengl/doc/openglrf/glXChooseVisual.htm%23b5c84be452rree */
- /* for the attribute declarations. Note that the attributes are different than those used in glxChooseVisual */
-
- if (!mode->IsIndexed) { attribs[i++] = GLX_RGBA; }
- attribs[i++] = GLX_RED_SIZE; attribs[i++] = mode->R;
- attribs[i++] = GLX_GREEN_SIZE; attribs[i++] = mode->G;
- attribs[i++] = GLX_BLUE_SIZE; attribs[i++] = mode->B;
- attribs[i++] = GLX_ALPHA_SIZE; attribs[i++] = mode->A;
- attribs[i++] = GLX_DEPTH_SIZE; attribs[i++] = depth;
-
- attribs[i++] = GLX_DOUBLEBUFFER;
- attribs[i++] = 0;
-}
-
-static XVisualInfo GLContext_SelectVisual(void) {
- int attribs[20];
- int major, minor;
- XVisualInfo* visual = NULL;
-
- int fbcount, screen;
- GLXFBConfig* fbconfigs;
- XVisualInfo info;
- struct GraphicsMode mode;
-
- InitGraphicsMode(&mode);
- GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH);
- screen = DefaultScreen(win_display);
-
- if (!glXQueryVersion(win_display, &major, &minor)) {
- Platform_LogConst("glXQueryVersion failed");
- } else if (major >= 1 && minor >= 3) {
- /* ChooseFBConfig returns an array of GLXFBConfig opaque structures */
- fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount);
- if (fbconfigs && fbcount) {
- /* Use the first GLXFBConfig from the fbconfigs array (best match) */
- visual = glXGetVisualFromFBConfig(win_display, *fbconfigs);
- XFree(fbconfigs);
- }
- }
-
- if (!visual) {
- Platform_LogConst("Falling back to glXChooseVisual.");
- visual = glXChooseVisual(win_display, screen, attribs);
- }
- /* Some really old devices will only supply 16 bit depths */
- if (!visual) {
- GetAttribs(&mode, attribs, 16);
- visual = glXChooseVisual(win_display, screen, attribs);
- }
- if (!visual) Logger_Abort("Requested GraphicsMode not available.");
-
- info = *visual;
- XFree(visual);
- return info;
-}
-
-
/*########################################################################################################################*
*-------------------------------------------------------AGL OpenGL--------------------------------------------------------*
*#########################################################################################################################*/
-#elif defined CC_BUILD_CARBON
+#if defined CC_BUILD_CARBON
#include
static AGLContext ctx_handle;
@@ -4212,68 +855,5 @@ void* GLContext_GetAddress(const char* function) {
}
void GLContext_GetApiInfo(cc_string* info) { }
-
-
-/*########################################################################################################################*
-*------------------------------------------------Emscripten WebGL context-------------------------------------------------*
-*#########################################################################################################################*/
-#elif defined CC_BUILD_WEB
-#include "Graphics.h"
-static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx_handle;
-
-static EM_BOOL GLContext_OnLost(int eventType, const void *reserved, void *userData) {
- Gfx_LoseContext("WebGL context lost");
- return 1;
-}
-
-void GLContext_Create(void) {
- EmscriptenWebGLContextAttributes attribs;
- emscripten_webgl_init_context_attributes(&attribs);
- attribs.alpha = false;
- attribs.depth = true;
- attribs.stencil = false;
- attribs.antialias = false;
-
- ctx_handle = emscripten_webgl_create_context("#canvas", &attribs);
- if (!ctx_handle) Window_ShowDialog("WebGL unsupported", "WebGL is required to run ClassiCube");
-
- emscripten_webgl_make_context_current(ctx_handle);
- emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, GLContext_OnLost);
-}
-
-void GLContext_Update(void) {
- /* TODO: do we need to do something here.... ? */
-}
-cc_bool GLContext_TryRestore(void) {
- return !emscripten_is_webgl_context_lost(0);
-}
-
-void GLContext_Free(void) {
- emscripten_webgl_destroy_context(ctx_handle);
- emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, NULL);
-}
-
-void* GLContext_GetAddress(const char* function) { return NULL; }
-cc_bool GLContext_SwapBuffers(void) { return true; /* Browser implicitly does this */ }
-
-void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
- if (vsync) {
- emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
- } else {
- emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, (int)minFrameMs);
- }
-}
-
-extern void interop_GetGpuRenderer(char* buffer, int len);
-void GLContext_GetApiInfo(cc_string* info) {
- char buffer[NATIVE_STR_LEN];
- int len;
- interop_GetGpuRenderer(buffer, NATIVE_STR_LEN);
-
- len = String_CalcLen(buffer, NATIVE_STR_LEN);
- if (!len) return;
- String_AppendConst(info, "GPU: ");
- String_AppendUtf8(info, buffer, len);
-}
#endif
#endif
diff --git a/src/Window_Android.c b/src/Window_Android.c
new file mode 100644
index 000000000..ebdaf40f6
--- /dev/null
+++ b/src/Window_Android.c
@@ -0,0 +1,410 @@
+#include "Core.h"
+#if defined CC_BUILD_ANDROID && !defined CC_BUILD_SDL
+#include "_WindowBase.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Errors.h"
+#include
+#include
+#include
+static ANativeWindow* win_handle;
+
+static void RefreshWindowBounds(void) {
+ WindowInfo.Width = ANativeWindow_getWidth(win_handle);
+ WindowInfo.Height = ANativeWindow_getHeight(win_handle);
+ Platform_Log2("SCREEN BOUNDS: %i,%i", &WindowInfo.Width, &WindowInfo.Height);
+ Event_RaiseVoid(&WindowEvents.Resized);
+}
+
+static int MapNativeKey(int code) {
+ if (code >= AKEYCODE_0 && code <= AKEYCODE_9) return (code - AKEYCODE_0) + '0';
+ if (code >= AKEYCODE_A && code <= AKEYCODE_Z) return (code - AKEYCODE_A) + 'A';
+ if (code >= AKEYCODE_F1 && code <= AKEYCODE_F12) return (code - AKEYCODE_F1) + KEY_F1;
+ if (code >= AKEYCODE_NUMPAD_0 && code <= AKEYCODE_NUMPAD_9) return (code - AKEYCODE_NUMPAD_0) + KEY_KP0;
+
+ switch (code) {
+ /* TODO: AKEYCODE_STAR */
+ /* TODO: AKEYCODE_POUND */
+ case AKEYCODE_BACK: return KEY_ESCAPE;
+ case AKEYCODE_COMMA: return KEY_COMMA;
+ case AKEYCODE_PERIOD: return KEY_PERIOD;
+ case AKEYCODE_ALT_LEFT: return KEY_LALT;
+ case AKEYCODE_ALT_RIGHT: return KEY_RALT;
+ case AKEYCODE_SHIFT_LEFT: return KEY_LSHIFT;
+ case AKEYCODE_SHIFT_RIGHT: return KEY_RSHIFT;
+ case AKEYCODE_TAB: return KEY_TAB;
+ case AKEYCODE_SPACE: return KEY_SPACE;
+ case AKEYCODE_ENTER: return KEY_ENTER;
+ case AKEYCODE_DEL: return KEY_BACKSPACE;
+ case AKEYCODE_GRAVE: return KEY_TILDE;
+ case AKEYCODE_MINUS: return KEY_MINUS;
+ case AKEYCODE_EQUALS: return KEY_EQUALS;
+ case AKEYCODE_LEFT_BRACKET: return KEY_LBRACKET;
+ case AKEYCODE_RIGHT_BRACKET: return KEY_RBRACKET;
+ case AKEYCODE_BACKSLASH: return KEY_BACKSLASH;
+ case AKEYCODE_SEMICOLON: return KEY_SEMICOLON;
+ case AKEYCODE_APOSTROPHE: return KEY_QUOTE;
+ case AKEYCODE_SLASH: return KEY_SLASH;
+ /* TODO: AKEYCODE_AT */
+ /* TODO: AKEYCODE_PLUS */
+ /* TODO: AKEYCODE_MENU */
+ case AKEYCODE_PAGE_UP: return KEY_PAGEUP;
+ case AKEYCODE_PAGE_DOWN: return KEY_PAGEDOWN;
+ case AKEYCODE_ESCAPE: return KEY_ESCAPE;
+ case AKEYCODE_FORWARD_DEL: return KEY_DELETE;
+ case AKEYCODE_CTRL_LEFT: return KEY_LCTRL;
+ case AKEYCODE_CTRL_RIGHT: return KEY_RCTRL;
+ case AKEYCODE_CAPS_LOCK: return KEY_CAPSLOCK;
+ case AKEYCODE_SCROLL_LOCK: return KEY_SCROLLLOCK;
+ case AKEYCODE_META_LEFT: return KEY_LWIN;
+ case AKEYCODE_META_RIGHT: return KEY_RWIN;
+ case AKEYCODE_SYSRQ: return KEY_PRINTSCREEN;
+ case AKEYCODE_BREAK: return KEY_PAUSE;
+ case AKEYCODE_INSERT: return KEY_INSERT;
+ case AKEYCODE_NUM_LOCK: return KEY_NUMLOCK;
+ case AKEYCODE_NUMPAD_DIVIDE: return KEY_KP_DIVIDE;
+ case AKEYCODE_NUMPAD_MULTIPLY: return KEY_KP_MULTIPLY;
+ case AKEYCODE_NUMPAD_SUBTRACT: return KEY_KP_MINUS;
+ case AKEYCODE_NUMPAD_ADD: return KEY_KP_PLUS;
+ case AKEYCODE_NUMPAD_DOT: return KEY_KP_DECIMAL;
+ case AKEYCODE_NUMPAD_ENTER: return KEY_KP_ENTER;
+ }
+ return KEY_NONE;
+}
+
+static void JNICALL java_processKeyDown(JNIEnv* env, jobject o, jint code) {
+ int key = MapNativeKey(code);
+ Platform_Log2("KEY - DOWN %i,%i", &code, &key);
+ if (key) Input_SetPressed(key);
+}
+
+static void JNICALL java_processKeyUp(JNIEnv* env, jobject o, jint code) {
+ int key = MapNativeKey(code);
+ Platform_Log2("KEY - UP %i,%i", &code, &key);
+ if (key) Input_SetReleased(key);
+}
+
+static void JNICALL java_processKeyChar(JNIEnv* env, jobject o, jint code) {
+ char keyChar;
+ int key = MapNativeKey(code);
+ Platform_Log2("KEY - PRESS %i,%i", &code, &key);
+
+ if (Convert_TryCodepointToCP437(code, &keyChar)) {
+ Event_RaiseInt(&InputEvents.Press, keyChar);
+ }
+}
+
+static void JNICALL java_processKeyText(JNIEnv* env, jobject o, jstring str) {
+ char buffer[NATIVE_STR_LEN];
+ cc_string text = JavaGetString(env, str, buffer);
+ Platform_Log1("KEY - TEXT %s", &text);
+ Event_RaiseString(&InputEvents.TextChanged, &text);
+}
+
+static void JNICALL java_processPointerDown(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
+ Platform_Log4("POINTER %i (%i) - DOWN %i,%i", &id, &isMouse, &x, &y);
+ Input_AddTouch(id, x, y);
+}
+
+static void JNICALL java_processPointerUp(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
+ Platform_Log4("POINTER %i (%i) - UP %i,%i", &id, &isMouse, &x, &y);
+ Input_RemoveTouch(id, x, y);
+}
+
+static void JNICALL java_processPointerMove(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
+ Platform_Log4("POINTER %i (%i) - MOVE %i,%i", &id, &isMouse, &x, &y);
+ Input_UpdateTouch(id, x, y);
+}
+
+static void JNICALL java_processSurfaceCreated(JNIEnv* env, jobject o, jobject surface) {
+ Platform_LogConst("WIN - CREATED");
+ win_handle = ANativeWindow_fromSurface(env, surface);
+ WindowInfo.Handle = win_handle;
+ RefreshWindowBounds();
+ /* TODO: Restore context */
+ Event_RaiseVoid(&WindowEvents.Created);
+}
+
+#include "Graphics.h"
+static void JNICALL java_processSurfaceDestroyed(JNIEnv* env, jobject o) {
+ Platform_LogConst("WIN - DESTROYED");
+ if (win_handle) ANativeWindow_release(win_handle);
+
+ win_handle = NULL;
+ WindowInfo.Handle = NULL;
+ /* eglSwapBuffers might return EGL_BAD_SURFACE, EGL_BAD_ALLOC, or some other error */
+ /* Instead the context is lost here in a consistent manner */
+ if (Gfx.Created) Gfx_LoseContext("surface lost");
+ JavaCallVoid(env, "processedSurfaceDestroyed", "()V", NULL);
+}
+
+static void JNICALL java_processSurfaceResized(JNIEnv* env, jobject o, jobject surface) {
+ Platform_LogConst("WIN - RESIZED");
+ RefreshWindowBounds();
+}
+
+static void JNICALL java_processSurfaceRedrawNeeded(JNIEnv* env, jobject o) {
+ Platform_LogConst("WIN - REDRAW");
+ Event_RaiseVoid(&WindowEvents.Redraw);
+}
+
+static void JNICALL java_onStart(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - ON START");
+}
+
+static void JNICALL java_onStop(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - ON STOP");
+}
+
+static void JNICALL java_onResume(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - ON RESUME");
+ /* TODO: Resume rendering */
+}
+
+static void JNICALL java_onPause(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - ON PAUSE");
+ /* TODO: Disable rendering */
+}
+
+static void JNICALL java_onDestroy(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - ON DESTROY");
+
+ if (WindowInfo.Exists) Window_Close();
+ /* TODO: signal to java code we're done */
+ JavaCallVoid(env, "processedDestroyed", "()V", NULL);
+}
+
+static void JNICALL java_onGotFocus(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - GOT FOCUS");
+ WindowInfo.Focused = true;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+}
+
+static void JNICALL java_onLostFocus(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - LOST FOCUS");
+ WindowInfo.Focused = false;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+ /* TODO: Disable rendering? */
+}
+
+static void JNICALL java_onLowMemory(JNIEnv* env, jobject o) {
+ Platform_LogConst("APP - LOW MEM");
+ /* TODO: Low memory */
+}
+
+static const JNINativeMethod methods[19] = {
+ { "processKeyDown", "(I)V", java_processKeyDown },
+ { "processKeyUp", "(I)V", java_processKeyUp },
+ { "processKeyChar", "(I)V", java_processKeyChar },
+ { "processKeyText", "(Ljava/lang/String;)V", java_processKeyText },
+
+ { "processPointerDown", "(IIII)V", java_processPointerDown },
+ { "processPointerUp", "(IIII)V", java_processPointerUp },
+ { "processPointerMove", "(IIII)V", java_processPointerMove },
+
+ { "processSurfaceCreated", "(Landroid/view/Surface;)V", java_processSurfaceCreated },
+ { "processSurfaceDestroyed", "()V", java_processSurfaceDestroyed },
+ { "processSurfaceResized", "(Landroid/view/Surface;)V", java_processSurfaceResized },
+ { "processSurfaceRedrawNeeded", "()V", java_processSurfaceRedrawNeeded },
+
+ { "processOnStart", "()V", java_onStart },
+ { "processOnStop", "()V", java_onStop },
+ { "processOnResume", "()V", java_onResume },
+ { "processOnPause", "()V", java_onPause },
+ { "processOnDestroy", "()V", java_onDestroy },
+
+ { "processOnGotFocus", "()V", java_onGotFocus },
+ { "processOnLostFocus", "()V", java_onLostFocus },
+ { "processOnLowMemory", "()V", java_onLowMemory }
+};
+
+void Window_Init(void) {
+ JNIEnv* env;
+ /* TODO: ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN, 0); */
+ JavaGetCurrentEnv(env);
+ JavaRegisterNatives(env, methods);
+
+ WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
+ Input_SetTouchMode(true);
+
+ DisplayInfo.Depth = 32;
+ DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL);
+ DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL);
+}
+
+void Window_Create(int width, int height) {
+ WindowInfo.Exists = true;
+ /* actual window creation is done when processSurfaceCreated is received */
+}
+
+static cc_bool winCreated;
+static void OnWindowCreated(void* obj) { winCreated = true; }
+cc_bool Window_RemakeSurface(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ winCreated = false;
+
+ /* Force window to be destroyed and re-created */
+ /* (see comments in setupForGame for why this has to be done) */
+ JavaCallVoid(env, "setupForGame", "()V", NULL);
+ Event_Register_(&WindowEvents.Created, NULL, OnWindowCreated);
+ Platform_LogConst("Entering wait for window exist loop..");
+
+ /* Loop until window gets created async */
+ while (WindowInfo.Exists && !winCreated) {
+ Window_ProcessEvents();
+ Thread_Sleep(10);
+ }
+
+ Platform_LogConst("OK window created..");
+ Event_Unregister_(&WindowEvents.Created, NULL, OnWindowCreated);
+ return winCreated;
+}
+
+void Window_SetTitle(const cc_string* title) {
+ /* TODO: Implement this somehow */
+ /* Maybe https://stackoverflow.com/questions/2198410/how-to-change-title-of-activity-in-android */
+}
+
+void Clipboard_GetText(cc_string* value) {
+ JavaCall_Void_String("getClipboardText", value);
+}
+void Clipboard_SetText(const cc_string* value) {
+ JavaCall_String_Void("setClipboardText", value);
+}
+
+void Window_Show(void) { } /* Window already visible */
+int Window_GetWindowState(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ return JavaCallInt(env, "getWindowState", "()I", NULL);
+}
+
+cc_result Window_EnterFullscreen(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ JavaCallVoid(env, "enterFullscreen", "()V", NULL);
+ return 0;
+}
+
+cc_result Window_ExitFullscreen(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ JavaCallVoid(env, "exitFullscreen", "()V", NULL);
+ return 0;
+}
+
+void Window_SetSize(int width, int height) { }
+
+void Window_Close(void) {
+ WindowInfo.Exists = false;
+ Event_RaiseVoid(&WindowEvents.Closing);
+ /* TODO: Do we need to call finish here */
+ /* ANativeActivity_finish(app->activity); */
+}
+
+void Window_ProcessEvents(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ /* TODO: Cache the java env and cache the method ID!!!!! */
+ JavaCallVoid(env, "processEvents", "()V", NULL);
+}
+
+/* No actual mouse cursor */
+static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
+void Cursor_SetPosition(int x, int y) { }
+static void Cursor_DoSetVisible(cc_bool visible) { }
+
+static void ShowDialogCore(const char* title, const char* msg) {
+ JNIEnv* env;
+ jvalue args[2];
+ JavaGetCurrentEnv(env);
+
+ Platform_LogConst(title);
+ Platform_LogConst(msg);
+ /* in case surface destroyed message has arrived */
+ Window_ProcessEvents();
+
+ args[0].l = JavaMakeConst(env, title);
+ args[1].l = JavaMakeConst(env, msg);
+ JavaCallVoid(env, "showAlert", "(Ljava/lang/String;Ljava/lang/String;)V", args);
+ (*env)->DeleteLocalRef(env, args[0].l);
+ (*env)->DeleteLocalRef(env, args[1].l);
+}
+
+static struct Bitmap fb_bmp;
+void Window_AllocFramebuffer(struct Bitmap* bmp) {
+ bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
+ fb_bmp = *bmp;
+}
+
+void Window_DrawFramebuffer(Rect2D r) {
+ ANativeWindow_Buffer buffer;
+ cc_uint32* src;
+ cc_uint32* dst;
+ ARect b;
+ int y, res, size;
+
+ /* window not created yet */
+ if (!win_handle) return;
+ b.left = r.X; b.right = r.X + r.Width;
+ b.top = r.Y; b.bottom = r.Y + r.Height;
+
+ /* Platform_Log4("DIRTY: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
+ res = ANativeWindow_lock(win_handle, &buffer, &b);
+ if (res) Logger_Abort2(res, "Locking window pixels");
+ /* Platform_Log4("ADJUS: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
+
+ /* In some rare cases, the returned locked region will be entire area of the surface */
+ /* This can cause a crash if the surface has been resized (i.e. device rotated), */
+ /* but the framebuffer has not been resized yet. So always constrain bounds. */
+ b.left = min(b.left, fb_bmp.width); b.right = min(b.right, fb_bmp.width);
+ b.top = min(b.top, fb_bmp.height); b.bottom = min(b.bottom, fb_bmp.height);
+
+ src = (cc_uint32*)fb_bmp.scan0 + b.left;
+ dst = (cc_uint32*)buffer.bits + b.left;
+ size = (b.right - b.left) * 4;
+
+ for (y = b.top; y < b.bottom; y++) {
+ Mem_Copy(dst + y * buffer.stride, src + y * fb_bmp.width, size);
+ }
+ res = ANativeWindow_unlockAndPost(win_handle);
+ if (res) Logger_Abort2(res, "Unlocking window pixels");
+}
+
+void Window_FreeFramebuffer(struct Bitmap* bmp) {
+ Mem_Free(bmp->scan0);
+}
+
+void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) {
+ JNIEnv* env;
+ jvalue args[2];
+ JavaGetCurrentEnv(env);
+
+ args[0].l = JavaMakeString(env, kArgs->text);
+ args[1].i = kArgs->type;
+ JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
+ (*env)->DeleteLocalRef(env, args[0].l);
+}
+
+void Window_SetKeyboardText(const cc_string* text) {
+ JNIEnv* env;
+ jvalue args[1];
+ JavaGetCurrentEnv(env);
+
+ args[0].l = JavaMakeString(env, text);
+ JavaCallVoid(env, "setKeyboardText", "(Ljava/lang/String;)V", args);
+ (*env)->DeleteLocalRef(env, args[0].l);
+}
+
+void Window_CloseKeyboard(void) {
+ JNIEnv* env;
+ JavaGetCurrentEnv(env);
+ JavaCallVoid(env, "closeKeyboard", "()V", NULL);
+}
+
+void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); }
+void Window_UpdateRawMouse(void) { }
+void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
+#endif
diff --git a/src/Window_SDL.c b/src/Window_SDL.c
new file mode 100644
index 000000000..269f78681
--- /dev/null
+++ b/src/Window_SDL.c
@@ -0,0 +1,358 @@
+#include "Core.h"
+#if defined CC_BUILD_SDL
+#include "_WindowBase.h"
+#include "Graphics.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Errors.h"
+#include
+static SDL_Window* win_handle;
+
+static void RefreshWindowBounds(void) {
+ SDL_GetWindowSize(win_handle, &WindowInfo.Width, &WindowInfo.Height);
+}
+
+static void Window_SDLFail(const char* place) {
+ char strBuffer[256];
+ cc_string str;
+ String_InitArray_NT(str, strBuffer);
+
+ String_Format2(&str, "Error when %c: %c", place, SDL_GetError());
+ str.buffer[str.length] = '\0';
+ Logger_Abort(str.buffer);
+}
+
+void Window_Init(void) {
+ SDL_DisplayMode mode = { 0 };
+ SDL_Init(SDL_INIT_VIDEO);
+ SDL_GetDesktopDisplayMode(0, &mode);
+
+ DisplayInfo.Width = mode.w;
+ DisplayInfo.Height = mode.h;
+ DisplayInfo.Depth = SDL_BITSPERPIXEL(mode.format);
+ DisplayInfo.ScaleX = 1;
+ DisplayInfo.ScaleY = 1;
+}
+
+void Window_Create(int width, int height) {
+ int x = Display_CentreX(width);
+ int y = Display_CentreY(height);
+
+ /* TODO: Don't set this flag for launcher window */
+ win_handle = SDL_CreateWindow(NULL, x, y, width, height, SDL_WINDOW_OPENGL);
+ if (!win_handle) Window_SDLFail("creating window");
+
+ RefreshWindowBounds();
+ WindowInfo.Exists = true;
+ WindowInfo.Handle = win_handle;
+}
+
+void Window_SetTitle(const cc_string* title) {
+ char str[NATIVE_STR_LEN];
+ Platform_EncodeUtf8(str, title);
+ SDL_SetWindowTitle(win_handle, str);
+}
+
+void Clipboard_GetText(cc_string* value) {
+ char* ptr = SDL_GetClipboardText();
+ if (!ptr) return;
+
+ int len = String_Length(ptr);
+ String_AppendUtf8(value, ptr, len);
+ SDL_free(ptr);
+}
+
+void Clipboard_SetText(const cc_string* value) {
+ char str[NATIVE_STR_LEN];
+ Platform_EncodeUtf8(str, value);
+ SDL_SetClipboardText(str);
+}
+
+void Window_Show(void) { SDL_ShowWindow(win_handle); }
+
+int Window_GetWindowState(void) {
+ Uint32 flags = SDL_GetWindowFlags(win_handle);
+
+ if (flags & SDL_WINDOW_MINIMIZED) return WINDOW_STATE_MINIMISED;
+ if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) return WINDOW_STATE_FULLSCREEN;
+ return WINDOW_STATE_NORMAL;
+}
+
+cc_result Window_EnterFullscreen(void) {
+ return SDL_SetWindowFullscreen(win_handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
+}
+cc_result Window_ExitFullscreen(void) { SDL_RestoreWindow(win_handle); return 0; }
+
+void Window_SetSize(int width, int height) {
+ SDL_SetWindowSize(win_handle, width, height);
+}
+
+void Window_Close(void) {
+ SDL_Event e;
+ e.type = SDL_QUIT;
+ SDL_PushEvent(&e);
+}
+
+static int MapNativeKey(SDL_Keycode k) {
+ if (k >= SDLK_0 && k <= SDLK_9) { return '0' + (k - SDLK_0); }
+ if (k >= SDLK_a && k <= SDLK_z) { return 'A' + (k - SDLK_a); }
+ if (k >= SDLK_F1 && k <= SDLK_F12) { return KEY_F1 + (k - SDLK_F1); }
+ if (k >= SDLK_F13 && k <= SDLK_F24) { return KEY_F13 + (k - SDLK_F13); }
+ /* SDLK_KP_0 isn't before SDLK_KP_1 */
+ if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return KEY_KP1 + (k - SDLK_KP_1); }
+
+ switch (k) {
+ case SDLK_RETURN: return KEY_ENTER;
+ case SDLK_ESCAPE: return KEY_ESCAPE;
+ case SDLK_BACKSPACE: return KEY_BACKSPACE;
+ case SDLK_TAB: return KEY_TAB;
+ case SDLK_SPACE: return KEY_SPACE;
+ case SDLK_QUOTE: return KEY_QUOTE;
+ case SDLK_EQUALS: return KEY_EQUALS;
+ case SDLK_COMMA: return KEY_COMMA;
+ case SDLK_MINUS: return KEY_MINUS;
+ case SDLK_PERIOD: return KEY_PERIOD;
+ case SDLK_SLASH: return KEY_SLASH;
+ case SDLK_SEMICOLON: return KEY_SEMICOLON;
+ case SDLK_LEFTBRACKET: return KEY_LBRACKET;
+ case SDLK_BACKSLASH: return KEY_BACKSLASH;
+ case SDLK_RIGHTBRACKET: return KEY_RBRACKET;
+ case SDLK_BACKQUOTE: return KEY_TILDE;
+ case SDLK_CAPSLOCK: return KEY_CAPSLOCK;
+ case SDLK_PRINTSCREEN: return KEY_PRINTSCREEN;
+ case SDLK_SCROLLLOCK: return KEY_SCROLLLOCK;
+ case SDLK_PAUSE: return KEY_PAUSE;
+ case SDLK_INSERT: return KEY_INSERT;
+ case SDLK_HOME: return KEY_HOME;
+ case SDLK_PAGEUP: return KEY_PAGEUP;
+ case SDLK_DELETE: return KEY_DELETE;
+ case SDLK_END: return KEY_END;
+ case SDLK_PAGEDOWN: return KEY_PAGEDOWN;
+ case SDLK_RIGHT: return KEY_RIGHT;
+ case SDLK_LEFT: return KEY_LEFT;
+ case SDLK_DOWN: return KEY_DOWN;
+ case SDLK_UP: return KEY_UP;
+
+ case SDLK_NUMLOCKCLEAR: return KEY_NUMLOCK;
+ case SDLK_KP_DIVIDE: return KEY_KP_DIVIDE;
+ case SDLK_KP_MULTIPLY: return KEY_KP_MULTIPLY;
+ case SDLK_KP_MINUS: return KEY_KP_MINUS;
+ case SDLK_KP_PLUS: return KEY_KP_PLUS;
+ case SDLK_KP_ENTER: return KEY_KP_ENTER;
+ case SDLK_KP_0: return KEY_KP0;
+ case SDLK_KP_PERIOD: return KEY_KP_DECIMAL;
+
+ case SDLK_LCTRL: return KEY_LCTRL;
+ case SDLK_LSHIFT: return KEY_LSHIFT;
+ case SDLK_LALT: return KEY_LALT;
+ case SDLK_LGUI: return KEY_LWIN;
+ case SDLK_RCTRL: return KEY_RCTRL;
+ case SDLK_RSHIFT: return KEY_RSHIFT;
+ case SDLK_RALT: return KEY_RALT;
+ case SDLK_RGUI: return KEY_RWIN;
+ }
+ return KEY_NONE;
+}
+
+static void OnKeyEvent(const SDL_Event* e) {
+ cc_bool pressed = e->key.state == SDL_PRESSED;
+ int key = MapNativeKey(e->key.keysym.sym);
+ if (key) Input_Set(key, pressed);
+}
+
+static void OnMouseEvent(const SDL_Event* e) {
+ cc_bool pressed = e->button.state == SDL_PRESSED;
+ int btn;
+ switch (e->button.button) {
+ case SDL_BUTTON_LEFT: btn = KEY_LMOUSE; break;
+ case SDL_BUTTON_MIDDLE: btn = KEY_MMOUSE; break;
+ case SDL_BUTTON_RIGHT: btn = KEY_RMOUSE; break;
+ case SDL_BUTTON_X1: btn = KEY_XBUTTON1; break;
+ case SDL_BUTTON_X2: btn = KEY_XBUTTON2; break;
+ default: return;
+ }
+ Input_Set(btn, pressed);
+}
+
+static void OnTextEvent(const SDL_Event* e) {
+ char buffer[SDL_TEXTINPUTEVENT_TEXT_SIZE];
+ cc_string str;
+ int i, len;
+
+ String_InitArray(str, buffer);
+ len = String_CalcLen(e->text.text, SDL_TEXTINPUTEVENT_TEXT_SIZE);
+ String_AppendUtf8(&str, e->text.text, len);
+
+ for (i = 0; i < str.length; i++) {
+ Event_RaiseInt(&InputEvents.Press, str.buffer[i]);
+ }
+}
+
+static void OnWindowEvent(const SDL_Event* e) {
+ switch (e->window.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ Event_RaiseVoid(&WindowEvents.Redraw);
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ RefreshWindowBounds();
+ Event_RaiseVoid(&WindowEvents.Resized);
+ break;
+ case SDL_WINDOWEVENT_MINIMIZED:
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ case SDL_WINDOWEVENT_RESTORED:
+ Event_RaiseVoid(&WindowEvents.StateChanged);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ WindowInfo.Focused = true;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ WindowInfo.Focused = false;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+ break;
+ case SDL_WINDOWEVENT_CLOSE:
+ Window_Close();
+ break;
+ }
+}
+
+void Window_ProcessEvents(void) {
+ SDL_Event e;
+ while (SDL_PollEvent(&e)) {
+ switch (e.type) {
+
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ OnKeyEvent(&e); break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ OnMouseEvent(&e); break;
+ case SDL_MOUSEWHEEL:
+ Mouse_ScrollWheel(e.wheel.y);
+ break;
+ case SDL_MOUSEMOTION:
+ Pointer_SetPosition(0, e.motion.x, e.motion.y);
+ if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel);
+ break;
+ case SDL_TEXTINPUT:
+ OnTextEvent(&e); break;
+ case SDL_WINDOWEVENT:
+ OnWindowEvent(&e); break;
+
+ case SDL_QUIT:
+ WindowInfo.Exists = false;
+ Event_RaiseVoid(&WindowEvents.Closing);
+ SDL_DestroyWindow(win_handle);
+ break;
+
+ case SDL_RENDER_DEVICE_RESET:
+ Gfx_LoseContext("SDL device reset event");
+ Gfx_RecreateContext();
+ break;
+ }
+ }
+}
+
+static void Cursor_GetRawPos(int* x, int* y) {
+ SDL_GetMouseState(x, y);
+}
+void Cursor_SetPosition(int x, int y) {
+ SDL_WarpMouseInWindow(win_handle, x, y);
+}
+
+static void Cursor_DoSetVisible(cc_bool visible) {
+ SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
+}
+
+static void ShowDialogCore(const char* title, const char* msg) {
+ SDL_ShowSimpleMessageBox(0, title, msg, win_handle);
+}
+
+static SDL_Surface* surface;
+void Window_AllocFramebuffer(struct Bitmap* bmp) {
+ surface = SDL_GetWindowSurface(win_handle);
+ if (!surface) Window_SDLFail("getting window surface");
+
+ if (SDL_MUSTLOCK(surface)) {
+ int ret = SDL_LockSurface(surface);
+ if (ret < 0) Window_SDLFail("locking window surface");
+ }
+ bmp->scan0 = surface->pixels;
+}
+
+void Window_DrawFramebuffer(Rect2D r) {
+ SDL_Rect rect;
+ rect.x = r.X; rect.w = r.Width;
+ rect.y = r.Y; rect.h = r.Height;
+ SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1);
+}
+
+void Window_FreeFramebuffer(struct Bitmap* bmp) {
+ /* SDL docs explicitly say to NOT free the surface */
+ /* https://wiki.libsdl.org/SDL_GetWindowSurface */
+ /* TODO: Do we still need to unlock it though? */
+}
+
+void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { SDL_StartTextInput(); }
+void Window_SetKeyboardText(const cc_string* text) { }
+void Window_CloseKeyboard(void) { SDL_StopTextInput(); }
+
+void Window_EnableRawMouse(void) {
+ RegrabMouse();
+ SDL_SetRelativeMouseMode(true);
+ Input_RawMode = true;
+}
+void Window_UpdateRawMouse(void) { CentreMousePosition(); }
+
+void Window_DisableRawMouse(void) {
+ RegrabMouse();
+ SDL_SetRelativeMouseMode(false);
+ Input_RawMode = false;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------OpenGL context------------------------------------------------------*
+*#########################################################################################################################*/
+#if defined CC_BUILD_GL && !defined CC_BUILD_EGL
+static SDL_GLContext win_ctx;
+
+void GLContext_Create(void) {
+ struct GraphicsMode mode;
+ InitGraphicsMode(&mode);
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, mode.R);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, mode.B);
+ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A);
+
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
+
+ win_ctx = SDL_GL_CreateContext(win_handle);
+ if (!win_ctx) Window_SDLFail("creating OpenGL context");
+}
+
+void GLContext_Update(void) { }
+cc_bool GLContext_TryRestore(void) { return true; }
+void GLContext_Free(void) {
+ SDL_GL_DeleteContext(win_ctx);
+ win_ctx = NULL;
+}
+
+void* GLContext_GetAddress(const char* function) {
+ return SDL_GL_GetProcAddress(function);
+}
+
+cc_bool GLContext_SwapBuffers(void) {
+ SDL_GL_SwapWindow(win_handle);
+ return true;
+}
+
+void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+ SDL_GL_SetSwapInterval(vsync);
+}
+void GLContext_GetApiInfo(cc_string* info) { }
+#endif
+#endif
diff --git a/src/Window_Web.c b/src/Window_Web.c
new file mode 100644
index 000000000..85c3c32fa
--- /dev/null
+++ b/src/Window_Web.c
@@ -0,0 +1,651 @@
+#include "Core.h"
+#if defined CC_BUILD_WEB && !defined CC_BUILD_SDL
+#include "_WindowBase.h"
+#include "Game.h"
+#include "String.h"
+#include "Funcs.h"
+#include "ExtMath.h"
+#include "Bitmap.h"
+#include "Errors.h"
+#include
+#include
+#include
+extern int interop_CanvasWidth(void);
+extern int interop_CanvasHeight(void);
+extern int interop_ScreenWidth(void);
+extern int interop_ScreenHeight(void);
+
+static cc_bool keyboardOpen, needResize;
+static int RawDpiScale(int x) { return (int)(x * emscripten_get_device_pixel_ratio()); }
+static int GetScreenWidth(void) { return RawDpiScale(interop_ScreenWidth()); }
+static int GetScreenHeight(void) { return RawDpiScale(interop_ScreenHeight()); }
+
+static void UpdateWindowBounds(void) {
+ int width = interop_CanvasWidth();
+ int height = interop_CanvasHeight();
+ if (width == WindowInfo.Width && height == WindowInfo.Height) return;
+
+ WindowInfo.Width = width;
+ WindowInfo.Height = height;
+ Event_RaiseVoid(&WindowEvents.Resized);
+}
+
+static void SetFullscreenBounds(void) {
+ int width = GetScreenWidth();
+ int height = GetScreenHeight();
+ emscripten_set_canvas_element_size("#canvas", width, height);
+}
+
+/* Browser only allows pointer lock requests in response to user input */
+static void DeferredEnableRawMouse(void) {
+ EmscriptenPointerlockChangeEvent status;
+ if (!Input_RawMode) return;
+
+ status.isActive = false;
+ emscripten_get_pointerlock_status(&status);
+ if (!status.isActive) emscripten_request_pointerlock("#canvas", false);
+}
+
+static EM_BOOL OnMouseWheel(int type, const EmscriptenWheelEvent* ev, void* data) {
+ /* TODO: The scale factor isn't standardised.. is there a better way though? */
+ Mouse_ScrollWheel(-Math_Sign(ev->deltaY));
+ DeferredEnableRawMouse();
+ return true;
+}
+
+static EM_BOOL OnMouseButton(int type, const EmscriptenMouseEvent* ev, void* data) {
+ cc_bool down = type == EMSCRIPTEN_EVENT_MOUSEDOWN;
+ /* https://stackoverflow.com/questions/60895686/how-to-get-mouse-buttons-4-5-browser-back-browser-forward-working-in-firef */
+ switch (ev->button) {
+ case 0: Input_Set(KEY_LMOUSE, down); break;
+ case 1: Input_Set(KEY_MMOUSE, down); break;
+ case 2: Input_Set(KEY_RMOUSE, down); break;
+ case 3: Input_Set(KEY_XBUTTON1, down); break;
+ case 4: Input_Set(KEY_XBUTTON2, down); break;
+ }
+
+ DeferredEnableRawMouse();
+ return true;
+}
+
+/* input coordinates are CSS pixels, remap to internal pixels */
+static void RescaleXY(int* x, int* y) {
+ double css_width, css_height;
+ emscripten_get_element_css_size("#canvas", &css_width, &css_height);
+
+ if (css_width && css_height) {
+ *x = (int)(*x * WindowInfo.Width / css_width );
+ *y = (int)(*y * WindowInfo.Height / css_height);
+ } else {
+ /* If css width or height is 0, something is bogus */
+ /* Better to avoid divsision by 0 in that case though */
+ }
+}
+
+static EM_BOOL OnMouseMove(int type, const EmscriptenMouseEvent* ev, void* data) {
+ int x, y, buttons = ev->buttons;
+ /* Set before position change, in case mouse buttons changed when outside window */
+ Input_SetNonRepeatable(KEY_LMOUSE, buttons & 0x01);
+ Input_SetNonRepeatable(KEY_RMOUSE, buttons & 0x02);
+ Input_SetNonRepeatable(KEY_MMOUSE, buttons & 0x04);
+
+ x = ev->targetX; y = ev->targetY;
+ RescaleXY(&x, &y);
+ Pointer_SetPosition(0, x, y);
+ if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, ev->movementX, ev->movementY);
+ return true;
+}
+
+/* TODO: Also query mouse coordinates globally (in OnMouseMove) and reuse interop_AdjustXY here */
+extern void interop_AdjustXY(int* x, int* y);
+
+static EM_BOOL OnTouchStart(int type, const EmscriptenTouchEvent* ev, void* data) {
+ const EmscriptenTouchPoint* t;
+ int i, x, y;
+ for (i = 0; i < ev->numTouches; ++i) {
+ t = &ev->touches[i];
+ if (!t->isChanged) continue;
+ x = t->targetX; y = t->targetY;
+
+ interop_AdjustXY(&x, &y);
+ RescaleXY(&x, &y);
+ Input_AddTouch(t->identifier, x, y);
+ }
+ /* Don't intercept touchstart events while keyboard is open, that way */
+ /* user can still touch to move the caret position in input textbox. */
+ return !keyboardOpen;
+}
+
+static EM_BOOL OnTouchMove(int type, const EmscriptenTouchEvent* ev, void* data) {
+ const EmscriptenTouchPoint* t;
+ int i, x, y;
+ for (i = 0; i < ev->numTouches; ++i) {
+ t = &ev->touches[i];
+ if (!t->isChanged) continue;
+ x = t->targetX; y = t->targetY;
+
+ interop_AdjustXY(&x, &y);
+ RescaleXY(&x, &y);
+ Input_UpdateTouch(t->identifier, x, y);
+ }
+ /* Don't intercept touchmove events while keyboard is open, that way */
+ /* user can still touch to move the caret position in input textbox. */
+ return !keyboardOpen;
+}
+
+static EM_BOOL OnTouchEnd(int type, const EmscriptenTouchEvent* ev, void* data) {
+ const EmscriptenTouchPoint* t;
+ int i, x, y;
+ for (i = 0; i < ev->numTouches; ++i) {
+ t = &ev->touches[i];
+ if (!t->isChanged) continue;
+ x = t->targetX; y = t->targetY;
+
+ interop_AdjustXY(&x, &y);
+ RescaleXY(&x, &y);
+ Input_RemoveTouch(t->identifier, x, y);
+ }
+ /* Don't intercept touchend events while keyboard is open, that way */
+ /* user can still touch to move the caret position in input textbox. */
+ return !keyboardOpen;
+}
+
+static EM_BOOL OnFocus(int type, const EmscriptenFocusEvent* ev, void* data) {
+ WindowInfo.Focused = type == EMSCRIPTEN_EVENT_FOCUS;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+ return true;
+}
+
+static EM_BOOL OnResize(int type, const EmscriptenUiEvent* ev, void *data) {
+ UpdateWindowBounds(); needResize = true;
+ return true;
+}
+/* This is only raised when going into fullscreen */
+static EM_BOOL OnCanvasResize(int type, const void* reserved, void *data) {
+ UpdateWindowBounds(); needResize = true;
+ return false;
+}
+static EM_BOOL OnFullscreenChange(int type, const EmscriptenFullscreenChangeEvent* ev, void *data) {
+ UpdateWindowBounds(); needResize = true;
+ return false;
+}
+
+static const char* OnBeforeUnload(int type, const void* ev, void *data) {
+ if (!Game_ShouldClose()) {
+ /* Exit pointer lock, otherwise when you press Ctrl+W, the */
+ /* cursor remains invisible in the confirmation dialog */
+ emscripten_exit_pointerlock();
+ return "You have unsaved changes. Are you sure you want to quit?";
+ }
+ Window_Close();
+ return NULL;
+}
+
+static int MapNativeKey(int k, int l) {
+ if (k >= '0' && k <= '9') return k;
+ if (k >= 'A' && k <= 'Z') return k;
+ if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
+ if (k >= DOM_VK_NUMPAD0 && k <= DOM_VK_NUMPAD9) { return KEY_KP0 + (k - DOM_VK_NUMPAD0); }
+
+ switch (k) {
+ case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
+ case DOM_VK_TAB: return KEY_TAB;
+ case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER;
+ case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT;
+ case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL;
+ case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT;
+ case DOM_VK_PAUSE: return KEY_PAUSE;
+ case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
+ case DOM_VK_ESCAPE: return KEY_ESCAPE;
+ case DOM_VK_SPACE: return KEY_SPACE;
+
+ case DOM_VK_PAGE_UP: return KEY_PAGEUP;
+ case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
+ case DOM_VK_END: return KEY_END;
+ case DOM_VK_HOME: return KEY_HOME;
+ case DOM_VK_LEFT: return KEY_LEFT;
+ case DOM_VK_UP: return KEY_UP;
+ case DOM_VK_RIGHT: return KEY_RIGHT;
+ case DOM_VK_DOWN: return KEY_DOWN;
+ case DOM_VK_PRINTSCREEN: return KEY_PRINTSCREEN;
+ case DOM_VK_INSERT: return KEY_INSERT;
+ case DOM_VK_DELETE: return KEY_DELETE;
+
+ case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
+ case DOM_VK_EQUALS: return KEY_EQUALS;
+ case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN;
+ case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
+ case DOM_VK_ADD: return KEY_KP_PLUS;
+ case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
+ case DOM_VK_DECIMAL: return KEY_KP_DECIMAL;
+ case DOM_VK_DIVIDE: return KEY_KP_DIVIDE;
+ case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
+ case DOM_VK_SCROLL_LOCK: return KEY_SCROLLLOCK;
+
+ case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
+ case DOM_VK_COMMA: return KEY_COMMA;
+ case DOM_VK_PERIOD: return KEY_PERIOD;
+ case DOM_VK_SLASH: return KEY_SLASH;
+ case DOM_VK_BACK_QUOTE: return KEY_TILDE;
+ case DOM_VK_OPEN_BRACKET: return KEY_LBRACKET;
+ case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
+ case DOM_VK_CLOSE_BRACKET: return KEY_RBRACKET;
+ case DOM_VK_QUOTE: return KEY_QUOTE;
+
+ /* chrome */
+ case 186: return KEY_SEMICOLON;
+ case 187: return KEY_EQUALS;
+ case 189: return KEY_MINUS;
+ }
+ return KEY_NONE;
+}
+
+static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) {
+ int key = MapNativeKey(ev->keyCode, ev->location);
+ /* iOS safari still sends backspace key events, don't intercept those */
+ if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false;
+
+ if (key) Input_SetPressed(key);
+ DeferredEnableRawMouse();
+ if (!key) return false;
+
+ /* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
+ /* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
+ /* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */
+ if (Key_IsAltPressed()) return true;
+ if (Key_IsWinPressed()) return key != 'C' && key != 'V';
+ if (Key_IsCtrlPressed()) return key != 'C' && key != 'V';
+
+ /* Space needs special handling, as intercepting this prevents the ' ' key press event */
+ /* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
+ if (key == KEY_SPACE) return !keyboardOpen;
+
+ /* Must not intercept KeyDown for regular keys, otherwise KeyPress doesn't get raised. */
+ /* However, do want to prevent browser's behaviour on F11, F5, home etc. */
+ /* e.g. not preventing F11 means browser makes page fullscreen instead of just canvas */
+ return (key >= KEY_F1 && key <= KEY_F24) || (key >= KEY_UP && key <= KEY_RIGHT) ||
+ (key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
+}
+
+static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) {
+ int key = MapNativeKey(ev->keyCode, ev->location);
+ if (key) Input_SetReleased(key);
+ DeferredEnableRawMouse();
+ return key != KEY_NONE;
+}
+
+static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
+ char keyChar;
+ DeferredEnableRawMouse();
+ /* When on-screen keyboard is open, we don't want to intercept any key presses, */
+ /* because they should be sent to the HTML text input instead. */
+ /* (Chrome for android sends keypresses sometimes for '0' to '9' keys) */
+ /* - If any keys are intercepted, this causes the HTML text input to become */
+ /* desynchronised from the chat/menu input widget the user sees in game. */
+ /* - This causes problems such as attempting to backspace all text later to */
+ /* not actually backspace everything. (because the HTML text input does not */
+ /* have these intercepted key presses in its text buffer) */
+ if (Input_TouchMode && keyboardOpen) return false;
+
+ /* Safari on macOS still sends a keypress event, which must not be cancelled */
+ /* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */
+ if (ev->metaKey) return false;
+
+ if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
+ Event_RaiseInt(&InputEvents.Press, keyChar);
+ }
+ return true;
+}
+
+/* Really old emscripten versions (e.g. 1.38.21) don't have this defined */
+/* Can't just use "#window", newer versions switched to const int instead */
+#ifndef EMSCRIPTEN_EVENT_TARGET_WINDOW
+#define EMSCRIPTEN_EVENT_TARGET_WINDOW "#window"
+#endif
+
+static void HookEvents(void) {
+ emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnMouseWheel);
+ emscripten_set_mousedown_callback("#canvas", NULL, 0, OnMouseButton);
+ emscripten_set_mouseup_callback("#canvas", NULL, 0, OnMouseButton);
+ emscripten_set_mousemove_callback("#canvas", NULL, 0, OnMouseMove);
+ emscripten_set_fullscreenchange_callback("#canvas", NULL, 0, OnFullscreenChange);
+
+ emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
+ emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize);
+ emscripten_set_beforeunload_callback( NULL, OnBeforeUnload);
+
+ emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown);
+ emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp);
+ emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress);
+
+ emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart);
+ emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchMove);
+ emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
+ emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
+}
+
+static void UnhookEvents(void) {
+ emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_mousedown_callback("#canvas", NULL, 0, NULL);
+ emscripten_set_mouseup_callback("#canvas", NULL, 0, NULL);
+ emscripten_set_mousemove_callback("#canvas", NULL, 0, NULL);
+
+ emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_beforeunload_callback( NULL, NULL);
+
+ emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+
+ emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+ emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
+}
+
+extern int interop_IsAndroid(void);
+extern int interop_IsIOS(void);
+extern void interop_AddClipboardListeners(void);
+extern void interop_ForceTouchPageLayout(void);
+void Window_Init(void) {
+ int is_ios, droid;
+ DisplayInfo.Width = GetScreenWidth();
+ DisplayInfo.Height = GetScreenHeight();
+ DisplayInfo.Depth = 24;
+
+ DisplayInfo.ScaleX = emscripten_get_device_pixel_ratio();
+ DisplayInfo.ScaleY = DisplayInfo.ScaleX;
+ interop_AddClipboardListeners();
+
+ droid = interop_IsAndroid();
+ is_ios = interop_IsIOS();
+ Input_SetTouchMode(is_ios || droid);
+
+ /* iOS shifts the whole webpage up when opening chat, which causes problems */
+ /* as the chat/send butons are positioned at the top of the canvas - they */
+ /* get pushed offscreen and can't be used at all anymore. So handle this */
+ /* case specially by positioning them at the bottom instead for iOS. */
+ WindowInfo.SoftKeyboard = is_ios ? SOFT_KEYBOARD_SHIFT : SOFT_KEYBOARD_RESIZE;
+
+ /* Let the webpage know it needs to force a mobile layout */
+ if (!Input_TouchMode) return;
+ interop_ForceTouchPageLayout();
+}
+
+extern void interop_InitContainer(void);
+void Window_Create(int width, int height) {
+ WindowInfo.Exists = true;
+ WindowInfo.Focused = true;
+ HookEvents();
+ /* Let the webpage decide on initial bounds */
+ WindowInfo.Width = interop_CanvasWidth();
+ WindowInfo.Height = interop_CanvasHeight();
+ interop_InitContainer();
+}
+
+extern void interop_SetPageTitle(const char* title);
+void Window_SetTitle(const cc_string* title) {
+ char str[NATIVE_STR_LEN];
+ Platform_EncodeUtf8(str, title);
+ interop_SetPageTitle(str);
+}
+
+static char pasteBuffer[512];
+static cc_string pasteStr;
+EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) {
+ Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
+}
+
+EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) {
+ String_InitArray(pasteStr, pasteBuffer);
+ String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048));
+}
+
+EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
+ Window_StoreClipboardText(src);
+ Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
+}
+
+extern void interop_TryGetClipboardText(void);
+void Clipboard_GetText(cc_string* value) {
+ /* Window_StoreClipboardText may or may not be called by this */
+ interop_TryGetClipboardText();
+
+ String_Copy(value, &pasteStr);
+ pasteStr.length = 0;
+}
+
+extern void interop_TrySetClipboardText(const char* text);
+void Clipboard_SetText(const cc_string* value) {
+ char str[NATIVE_STR_LEN];
+ Platform_EncodeUtf8(str, value);
+ interop_TrySetClipboardText(str);
+}
+
+void Window_Show(void) { }
+
+int Window_GetWindowState(void) {
+ EmscriptenFullscreenChangeEvent status = { 0 };
+ emscripten_get_fullscreen_status(&status);
+ return status.isFullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL;
+}
+
+extern int interop_GetContainerID(void);
+extern void interop_EnterFullscreen(void);
+cc_result Window_EnterFullscreen(void) {
+ EmscriptenFullscreenStrategy strategy;
+ const char* target;
+ int res;
+ strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
+ strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
+ strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
+
+ strategy.canvasResizedCallback = OnCanvasResize;
+ strategy.canvasResizedCallbackUserData = NULL;
+
+ /* TODO: Return container element ID instead of hardcoding here */
+ res = interop_GetContainerID();
+ target = res ? "canvas_wrapper" : "#canvas";
+
+ res = emscripten_request_fullscreen_strategy(target, 1, &strategy);
+ if (res == EMSCRIPTEN_RESULT_NOT_SUPPORTED) res = ERR_NOT_SUPPORTED;
+ if (res) return res;
+
+ interop_EnterFullscreen();
+ return 0;
+}
+
+cc_result Window_ExitFullscreen(void) {
+ emscripten_exit_fullscreen();
+ UpdateWindowBounds();
+ return 0;
+}
+
+void Window_SetSize(int width, int height) {
+ emscripten_set_canvas_element_size("#canvas", width, height);
+ /* CSS size is in CSS units not pixel units */
+ emscripten_set_element_css_size("#canvas", width / DisplayInfo.ScaleX, height / DisplayInfo.ScaleY);
+ UpdateWindowBounds();
+}
+
+void Window_Close(void) {
+ WindowInfo.Exists = false;
+ Event_RaiseVoid(&WindowEvents.Closing);
+ /* If the game is closed while in fullscreen, the last rendered frame stays */
+ /* shown in fullscreen, but the game can't be interacted with anymore */
+ Window_ExitFullscreen();
+
+ /* Don't want cursor stuck on the dead 0,0 canvas */
+ Window_DisableRawMouse();
+ Window_SetSize(0, 0);
+ UnhookEvents();
+}
+
+extern void interop_RequestCanvasResize(void);
+void Window_ProcessEvents(void) {
+ if (!needResize) return;
+ needResize = false;
+ if (!WindowInfo.Exists) return;
+
+ if (Window_GetWindowState() == WINDOW_STATE_FULLSCREEN) {
+ SetFullscreenBounds();
+ } else {
+ /* Webpage can adjust canvas size if it wants to */
+ interop_RequestCanvasResize();
+ }
+ UpdateWindowBounds();
+}
+
+/* Not needed because browser provides relative mouse and touch events */
+static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
+/* Not allowed to move cursor from javascript */
+void Cursor_SetPosition(int x, int y) { }
+
+extern void interop_SetCursorVisible(int visible);
+static void Cursor_DoSetVisible(cc_bool visible) {
+ interop_SetCursorVisible(visible);
+}
+
+extern void interop_ShowDialog(const char* title, const char* msg);
+static void ShowDialogCore(const char* title, const char* msg) {
+ interop_ShowDialog(title, msg);
+}
+
+static OpenFileDialogCallback uploadCallback;
+EMSCRIPTEN_KEEPALIVE void Window_OnFileUploaded(const char* src) {
+ cc_string file; char buffer[FILENAME_SIZE];
+ String_InitArray(file, buffer);
+
+ String_AppendUtf8(&file, src, String_Length(src));
+ uploadCallback(&file);
+ uploadCallback = NULL;
+}
+
+extern void interop_OpenFileDialog(const char* filter);
+cc_result Window_OpenFileDialog(const char* filter, OpenFileDialogCallback callback) {
+ uploadCallback = callback;
+ /* Calls Window_OnFileUploaded on success */
+ interop_OpenFileDialog(filter);
+ return ERR_NOT_SUPPORTED;
+}
+
+void Window_AllocFramebuffer(struct Bitmap* bmp) { }
+void Window_DrawFramebuffer(Rect2D r) { }
+void Window_FreeFramebuffer(struct Bitmap* bmp) { }
+
+extern void interop_OpenKeyboard(const char* text, int type, const char* placeholder);
+extern void interop_SetKeyboardText(const char* text);
+extern void interop_CloseKeyboard(void);
+
+EMSCRIPTEN_KEEPALIVE void Window_OnTextChanged(const char* src) {
+ cc_string str; char buffer[800];
+ String_InitArray(str, buffer);
+
+ String_AppendUtf8(&str, src, String_CalcLen(src, 3200));
+ Event_RaiseString(&InputEvents.TextChanged, &str);
+}
+
+void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
+ char str[NATIVE_STR_LEN];
+ keyboardOpen = true;
+ if (!Input_TouchMode) return;
+
+ Platform_EncodeUtf8(str, args->text);
+ Platform_LogConst("OPEN SESAME");
+ interop_OpenKeyboard(str, args->type, args->placeholder);
+}
+
+void Window_SetKeyboardText(const cc_string* text) {
+ char str[NATIVE_STR_LEN];
+ if (!Input_TouchMode) return;
+
+ Platform_EncodeUtf8(str, text);
+ interop_SetKeyboardText(str);
+}
+
+void Window_CloseKeyboard(void) {
+ keyboardOpen = false;
+ if (!Input_TouchMode) return;
+ interop_CloseKeyboard();
+}
+
+void Window_EnableRawMouse(void) {
+ RegrabMouse();
+ /* defer pointerlock request until next user input */
+ Input_RawMode = true;
+}
+void Window_UpdateRawMouse(void) { }
+
+void Window_DisableRawMouse(void) {
+ RegrabMouse();
+ emscripten_exit_pointerlock();
+ Input_RawMode = false;
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------Emscripten WebGL context-------------------------------------------------*
+*#########################################################################################################################*/
+#ifdef CC_BUILD_GL
+#include "Graphics.h"
+static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx_handle;
+
+static EM_BOOL GLContext_OnLost(int eventType, const void *reserved, void *userData) {
+ Gfx_LoseContext("WebGL context lost");
+ return 1;
+}
+
+void GLContext_Create(void) {
+ EmscriptenWebGLContextAttributes attribs;
+ emscripten_webgl_init_context_attributes(&attribs);
+ attribs.alpha = false;
+ attribs.depth = true;
+ attribs.stencil = false;
+ attribs.antialias = false;
+
+ ctx_handle = emscripten_webgl_create_context("#canvas", &attribs);
+ if (!ctx_handle) Window_ShowDialog("WebGL unsupported", "WebGL is required to run ClassiCube");
+
+ emscripten_webgl_make_context_current(ctx_handle);
+ emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, GLContext_OnLost);
+}
+
+void GLContext_Update(void) {
+ /* TODO: do we need to do something here.... ? */
+}
+cc_bool GLContext_TryRestore(void) {
+ return !emscripten_is_webgl_context_lost(0);
+}
+
+void GLContext_Free(void) {
+ emscripten_webgl_destroy_context(ctx_handle);
+ emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, NULL);
+}
+
+void* GLContext_GetAddress(const char* function) { return NULL; }
+cc_bool GLContext_SwapBuffers(void) { return true; /* Browser implicitly does this */ }
+
+void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+ if (vsync) {
+ emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
+ } else {
+ emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, (int)minFrameMs);
+ }
+}
+
+extern void interop_GetGpuRenderer(char* buffer, int len);
+void GLContext_GetApiInfo(cc_string* info) {
+ char buffer[NATIVE_STR_LEN];
+ int len;
+ interop_GetGpuRenderer(buffer, NATIVE_STR_LEN);
+
+ len = String_CalcLen(buffer, NATIVE_STR_LEN);
+ if (!len) return;
+ String_AppendConst(info, "GPU: ");
+ String_AppendUtf8(info, buffer, len);
+}
+#endif
+#endif
diff --git a/src/Window_Win.c b/src/Window_Win.c
new file mode 100644
index 000000000..3c63ba51a
--- /dev/null
+++ b/src/Window_Win.c
@@ -0,0 +1,667 @@
+#include "Core.h"
+#if defined CC_BUILD_WINGUI && !defined CC_BUILD_SDL
+#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
+
+#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
+
+typedef BOOL (WINAPI *FUNC_RegisterRawInput)(PCRAWINPUTDEVICE devices, UINT numDevices, UINT size);
+static FUNC_RegisterRawInput _registerRawInput;
+typedef UINT (WINAPI *FUNC_GetRawInputData)(HRAWINPUT hRawInput, UINT cmd, void* data, UINT* size, UINT headerSize);
+static FUNC_GetRawInputData _getRawInputData;
+
+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 int windowX, windowY;
+static cc_bool is_ansiWindow;
+
+static const cc_uint8 key_map[14 * 16] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, KEY_BACKSPACE, KEY_TAB, 0, 0, 0, KEY_ENTER, 0, 0,
+ 0, 0, 0, KEY_PAUSE, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, 0, 0, 0, 0,
+ KEY_SPACE, KEY_PAGEUP, KEY_PAGEDOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, 0, KEY_PRINTSCREEN, 0, KEY_PRINTSCREEN, KEY_INSERT, KEY_DELETE, 0,
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
+ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', KEY_LWIN, KEY_RWIN, KEY_MENU, 0, 0,
+ KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP_MULTIPLY, KEY_KP_PLUS, 0, KEY_KP_MINUS, KEY_KP_DECIMAL, KEY_KP_DIVIDE,
+ KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16,
+ KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_NUMLOCK, KEY_SCROLLLOCK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_SEMICOLON, KEY_EQUALS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH,
+ KEY_TILDE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_LBRACKET, KEY_BACKSLASH, KEY_RBRACKET, KEY_QUOTE, 0,
+};
+static int MapNativeKey(WPARAM key, LPARAM meta) {
+ LPARAM ext = meta & (1UL << 24);
+ switch (key)
+ {
+ case VK_CONTROL:
+ return ext ? KEY_RCTRL : KEY_LCTRL;
+ case VK_MENU:
+ return ext ? KEY_RALT : KEY_LALT;
+ case VK_RETURN:
+ return ext ? KEY_KP_ENTER : KEY_ENTER;
+ default:
+ return key < Array_Elems(key_map) ? key_map[key] : 0;
+ }
+}
+
+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);
+ WindowInfo.Width = Rect_Width(rect);
+ WindowInfo.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 LRESULT CALLBACK Window_Procedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
+ char keyChar;
+ float wheelDelta;
+
+ switch (message) {
+ case WM_ACTIVATE:
+ WindowInfo.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.Redraw);
+ return 0;
+
+ case WM_WINDOWPOSCHANGED:
+ {
+ WINDOWPOS* pos = (WINDOWPOS*)lParam;
+ if (pos->hwnd != win_handle) break;
+ cc_bool sized = pos->cx != win_totalWidth || pos->cy != win_totalHeight;
+
+ 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 */
+ if (Convert_TryCodepointToCP437((cc_unichar)wParam, &keyChar)) {
+ Event_RaiseInt(&InputEvents.Press, keyChar);
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ /* Set before position change, in case mouse buttons changed when outside window */
+ Input_SetNonRepeatable(KEY_LMOUSE, wParam & 0x01);
+ Input_SetNonRepeatable(KEY_RMOUSE, wParam & 0x02);
+ Input_SetNonRepeatable(KEY_MMOUSE, 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_ScrollWheel(wheelDelta);
+ return 0;
+
+ case WM_LBUTTONDOWN:
+ Input_SetPressed(KEY_LMOUSE); break;
+ case WM_MBUTTONDOWN:
+ Input_SetPressed(KEY_MMOUSE); break;
+ case WM_RBUTTONDOWN:
+ Input_SetPressed(KEY_RMOUSE); break;
+ case WM_XBUTTONDOWN:
+ Input_SetPressed(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
+ break;
+
+ case WM_LBUTTONUP:
+ Input_SetReleased(KEY_LMOUSE); break;
+ case WM_MBUTTONUP:
+ Input_SetReleased(KEY_MMOUSE); break;
+ case WM_RBUTTONUP:
+ Input_SetReleased(KEY_RMOUSE); break;
+ case WM_XBUTTONUP:
+ Input_SetReleased(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
+ break;
+
+ case WM_INPUT:
+ {
+ RAWINPUT raw;
+ UINT ret, rawSize = sizeof(RAWINPUT);
+ int dx, dy;
+
+ 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) {
+ dx = raw.data.mouse.lLastX;
+ dy = raw.data.mouse.lLastY;
+ } else if (raw.data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
+ static int prevPosX, prevPosY;
+ dx = raw.data.mouse.lLastX - prevPosX;
+ dy = raw.data.mouse.lLastY - prevPosY;
+
+ prevPosX = raw.data.mouse.lLastX;
+ prevPosY = raw.data.mouse.lLastY;
+ } 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(KEY_LSHIFT, lShiftDown);
+ Input_Set(KEY_RSHIFT, rShiftDown);
+ }
+ } else {
+ key = MapNativeKey(wParam, lParam);
+ if (key) Input_Set(key, pressed);
+ else Platform_Log1("Unknown key: %x", &wParam);
+ }
+ 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 (WindowInfo.Exists) DestroyWindow(win_handle);
+ WindowInfo.Exists = false;
+ break;
+
+ case WM_DESTROY:
+ WindowInfo.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_Init(void) {
+ HDC 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);
+}
+
+static ATOM DoRegisterClass(void) {
+ ATOM atom;
+ WNDCLASSEXW wc = { 0 };
+ wc.cbSize = sizeof(WNDCLASSEXW);
+ wc.style = CS_OWNDC;
+ 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, IDC_ARROW);
+
+ if ((atom = RegisterClassExW(&wc))) return atom;
+ /* Windows 9x does not support W API functions */
+ return RegisterClassExA((const WNDCLASSEXA*)&wc);
+}
+
+static void DoCreateWindow(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, 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");
+}
+
+void Window_Create(int width, int height) {
+ ATOM atom;
+ win_instance = GetModuleHandleA(NULL);
+ /* TODO: UngroupFromTaskbar(); */
+ width = Display_ScaleX(width);
+ height = Display_ScaleY(height);
+
+ atom = DoRegisterClass();
+ DoCreateWindow(atom, width, height);
+ RefreshWindowBounds();
+
+ win_DC = GetDC(win_handle);
+ if (!win_DC) Logger_Abort2(GetLastError(), "Failed to get device context");
+ WindowInfo.Exists = true;
+ WindowInfo.Handle = win_handle;
+}
+
+void Window_SetTitle(const cc_string* title) {
+ WCHAR str[NATIVE_STR_LEN];
+ Platform_EncodeUtf16(str, title);
+ if (SetWindowTextW(win_handle, str)) return;
+
+ /* Windows 9x does not support W API functions */
+ Platform_Utf16ToAnsi(str);
+ SetWindowTextA(win_handle, (const char*)str);
+}
+
+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;
+ }
+}
+
+void Window_Show(void) {
+ ShowWindow(win_handle, SW_SHOW);
+ BringWindowToTop(win_handle);
+ SetForegroundWindow(win_handle);
+}
+
+int Window_GetWindowState(void) {
+ DWORD s = GetWindowLongW(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 */
+ SetWindowLongW(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();
+ }
+ 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;
+}
+
+
+void Window_SetSize(int width, int height) {
+ DWORD style = GetWindowLongW(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_Close(void) {
+ PostMessageW(win_handle, WM_CLOSE, 0, 0);
+}
+
+void Window_ProcessEvents(void) {
+ 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) {
+ WindowInfo.Focused = foreground == win_handle;
+ }
+}
+
+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 */
+ if (visible) {
+ for (i = 0; i < 10 && ShowCursor(true) < 0; i++) { }
+ } else {
+ for (i = 0; i < 10 && ShowCursor(false) >= 0; i++) {}
+ }
+}
+
+static void ShowDialogCore(const char* title, const char* msg) {
+ MessageBoxA(win_handle, msg, title, 0);
+}
+
+static HDC draw_DC;
+static HBITMAP draw_DIB;
+void Window_AllocFramebuffer(struct Bitmap* bmp) {
+ 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 = bmp->width;
+ hdr.bmiHeader.biHeight = -bmp->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");
+}
+
+void Window_DrawFramebuffer(Rect2D r) {
+ 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) {
+ static const cc_string user32 = String_FromConst("USER32.DLL");
+ void* lib;
+ RAWINPUTDEVICE rid;
+
+ if ((lib = DynamicLib_Load2(&user32))) {
+ _registerRawInput = (FUNC_RegisterRawInput)DynamicLib_Get2(lib, "RegisterRawInputDevices");
+ _getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
+ rawMouseSupported = _registerRawInput && _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 (_registerRawInput(&rid, 1, sizeof(rid))) return;
+ Logger_SysWarn(GetLastError(), "initing raw mouse");
+ rawMouseSupported = false;
+}
+
+void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
+void Window_SetKeyboardText(const cc_string* text) { }
+void Window_CloseKeyboard(void) { }
+
+void Window_EnableRawMouse(void) {
+ DefaultEnableRawMouse();
+ if (!rawMouseInited) InitRawMouse();
+ rawMouseInited = true;
+}
+
+void Window_UpdateRawMouse(void) {
+ if (rawMouseSupported) {
+ /* handled in WM_INPUT messages */
+ CentreMousePosition();
+ } else {
+ DefaultUpdateRawMouse();
+ }
+}
+
+void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------WGL OpenGL--------------------------------------------------------*
+*#########################################################################################################################*/
+#if defined CC_BUILD_GL && !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 };
+ 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 = mode->IsIndexed ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
+ pfd.cRedBits = mode->R;
+ pfd.cGreenBits = mode->G;
+ pfd.cBlueBits = mode->B;
+ pfd.cAlphaBits = mode->A;
+
+ int 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;
+
+ 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) 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;
+}
+
+void* GLContext_GetAddress(const char* function) {
+ void* addr = (void*)wglGetProcAddress(function);
+ return GLContext_IsInvalidAddress(addr) ? NULL : addr;
+}
+
+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
diff --git a/src/Window_X11.c b/src/Window_X11.c
new file mode 100644
index 000000000..2109a3e34
--- /dev/null
+++ b/src/Window_X11.c
@@ -0,0 +1,1241 @@
+#include "Core.h"
+#if defined CC_BUILD_X11 && !defined CC_BUILD_SDL
+#include "_WindowBase.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Options.h"
+#include "Errors.h"
+#include
+#include
+#include
+#include
+
+#ifdef X_HAVE_UTF8_STRING
+#define CC_BUILD_XIM
+/* XIM support based off details described in */
+/* https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/ */
+#endif
+
+#define _NET_WM_STATE_REMOVE 0
+#define _NET_WM_STATE_ADD 1
+#define _NET_WM_STATE_TOGGLE 2
+
+static Display* win_display;
+static Window win_rootWin, win_handle;
+static XVisualInfo win_visual;
+#ifdef CC_BUILD_XIM
+static XIM win_xim;
+static XIC win_xic;
+#endif
+
+static Atom wm_destroy, net_wm_state, net_wm_ping;
+static Atom net_wm_state_minimized;
+static Atom net_wm_state_fullscreen;
+
+static Atom xa_clipboard, xa_targets, xa_utf8_string, xa_data_sel;
+static Atom xa_atom = 4;
+static cc_bool grabCursor;
+static long win_eventMask = StructureNotifyMask | /* SubstructureNotifyMask | */
+ ExposureMask | KeyReleaseMask | KeyPressMask | KeymapStateMask |
+ PointerMotionMask | FocusChangeMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
+
+static int MapNativeKey(KeySym key, unsigned int state) {
+ if (key >= XK_0 && key <= XK_9) { return '0' + (key - XK_0); }
+ if (key >= XK_A && key <= XK_Z) { return 'A' + (key - XK_A); }
+ if (key >= XK_a && key <= XK_z) { return 'A' + (key - XK_a); }
+
+ if (key >= XK_F1 && key <= XK_F24) { return KEY_F1 + (key - XK_F1); }
+ if (key >= XK_KP_0 && key <= XK_KP_9) { return KEY_KP0 + (key - XK_KP_0); }
+
+ /* Same Num Lock behaviour as Windows and text editors */
+ if (key >= XK_KP_Home && key <= XK_KP_Delete && !(state & Mod2Mask)) {
+ if (key == XK_KP_Home) return KEY_HOME;
+ if (key == XK_KP_Up) return KEY_UP;
+ if (key == XK_KP_Page_Up) return KEY_PAGEUP;
+
+ if (key == XK_KP_Left) return KEY_LEFT;
+ if (key == XK_KP_Insert) return KEY_INSERT;
+ if (key == XK_KP_Right) return KEY_RIGHT;
+
+ if (key == XK_KP_End) return KEY_END;
+ if (key == XK_KP_Down) return KEY_DOWN;
+ if (key == XK_KP_Page_Down) return KEY_PAGEDOWN;
+ }
+
+ /* A chromebook user reported issues with pressing some keys: */
+ /* tilde - "Unknown key press: (8000060, 800007E) */
+ /* quote - "Unknown key press: (8000027, 8000022) */
+ /* Note if 8000 is stripped, you get '0060' (XK_grave) and 0027 (XK_apostrophe) */
+ /* ChromeOS seems to also mask to 0xFFFF, so I also do so here */
+ /* https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_x.cc */
+ key &= 0xFFFF;
+
+ switch (key) {
+ case XK_Escape: return KEY_ESCAPE;
+ case XK_Return: return KEY_ENTER;
+ case XK_space: return KEY_SPACE;
+ case XK_BackSpace: return KEY_BACKSPACE;
+
+ case XK_Shift_L: return KEY_LSHIFT;
+ case XK_Shift_R: return KEY_RSHIFT;
+ case XK_Alt_L: return KEY_LALT;
+ case XK_Alt_R: return KEY_RALT;
+ case XK_Control_L: return KEY_LCTRL;
+ case XK_Control_R: return KEY_RCTRL;
+ case XK_Super_L: return KEY_LWIN;
+ case XK_Super_R: return KEY_RWIN;
+ case XK_Meta_L: return KEY_LWIN;
+ case XK_Meta_R: return KEY_RWIN;
+
+ case XK_Menu: return KEY_MENU;
+ case XK_Tab: return KEY_TAB;
+ case XK_minus: return KEY_MINUS;
+ case XK_plus: return KEY_EQUALS;
+ case XK_equal: return KEY_EQUALS;
+
+ case XK_Caps_Lock: return KEY_CAPSLOCK;
+ case XK_Num_Lock: return KEY_NUMLOCK;
+
+ case XK_Pause: return KEY_PAUSE;
+ case XK_Break: return KEY_PAUSE;
+ case XK_Scroll_Lock: return KEY_SCROLLLOCK;
+ case XK_Insert: return KEY_INSERT;
+ case XK_Print: return KEY_PRINTSCREEN;
+ case XK_Sys_Req: return KEY_PRINTSCREEN;
+
+ case XK_backslash: return KEY_BACKSLASH;
+ case XK_bar: return KEY_BACKSLASH;
+ case XK_braceleft: return KEY_LBRACKET;
+ case XK_bracketleft: return KEY_LBRACKET;
+ case XK_braceright: return KEY_RBRACKET;
+ case XK_bracketright: return KEY_RBRACKET;
+ case XK_colon: return KEY_SEMICOLON;
+ case XK_semicolon: return KEY_SEMICOLON;
+ case XK_quoteright: return KEY_QUOTE;
+ case XK_quotedbl: return KEY_QUOTE;
+ case XK_quoteleft: return KEY_TILDE;
+ case XK_asciitilde: return KEY_TILDE;
+
+ case XK_comma: return KEY_COMMA;
+ case XK_less: return KEY_COMMA;
+ case XK_period: return KEY_PERIOD;
+ case XK_greater: return KEY_PERIOD;
+ case XK_slash: return KEY_SLASH;
+ case XK_question: return KEY_SLASH;
+
+ case XK_Left: return KEY_LEFT;
+ case XK_Down: return KEY_DOWN;
+ case XK_Right: return KEY_RIGHT;
+ case XK_Up: return KEY_UP;
+
+ case XK_Delete: return KEY_DELETE;
+ case XK_Home: return KEY_HOME;
+ case XK_End: return KEY_END;
+ case XK_Page_Up: return KEY_PAGEUP;
+ case XK_Page_Down: return KEY_PAGEDOWN;
+
+ case XK_KP_Add: return KEY_KP_PLUS;
+ case XK_KP_Subtract: return KEY_KP_MINUS;
+ case XK_KP_Multiply: return KEY_KP_MULTIPLY;
+ case XK_KP_Divide: return KEY_KP_DIVIDE;
+ case XK_KP_Decimal: return KEY_KP_DECIMAL;
+ case XK_KP_Insert: return KEY_KP0;
+ case XK_KP_End: return KEY_KP1;
+ case XK_KP_Down: return KEY_KP2;
+ case XK_KP_Page_Down: return KEY_KP3;
+ case XK_KP_Left: return KEY_KP4;
+ case XK_KP_Begin: return KEY_KP5;
+ case XK_KP_Right: return KEY_KP6;
+ case XK_KP_Home: return KEY_KP7;
+ case XK_KP_Up: return KEY_KP8;
+ case XK_KP_Page_Up: return KEY_KP9;
+ case XK_KP_Delete: return KEY_KP_DECIMAL;
+ case XK_KP_Enter: return KEY_KP_ENTER;
+ }
+ return KEY_NONE;
+}
+
+/* NOTE: This may not be entirely accurate, because user can configure keycode mappings */
+static const cc_uint8 keycodeMap[136] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', '0', KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
+ 'O', 'P', KEY_LBRACKET, KEY_RBRACKET, KEY_ENTER, KEY_LCTRL, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', KEY_SEMICOLON,
+ KEY_QUOTE, KEY_TILDE, KEY_LSHIFT, KEY_BACKSLASH, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', KEY_PERIOD, KEY_COMMA, KEY_SLASH, KEY_RSHIFT, KEY_KP_MULTIPLY,
+ KEY_LALT, KEY_SPACE, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_KP7,
+ KEY_KP8, KEY_KP9, KEY_KP_MINUS, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP_PLUS, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP0, KEY_KP_DECIMAL, 0, 0, 0, KEY_F11,
+ KEY_F12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_RALT, KEY_RCTRL, KEY_HOME, KEY_UP, KEY_PAGEUP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, 0, 0, 0, 0,
+ 0, 0, 0, KEY_PAUSE, 0, 0, 0, 0, 0, KEY_LWIN, 0, KEY_RWIN
+};
+
+static int MapNativeKeycode(unsigned int keycode) {
+ return keycode < Array_Elems(keycodeMap) ? keycodeMap[keycode] : 0;
+}
+
+static void RegisterAtoms(void) {
+ Display* display = win_display;
+ wm_destroy = XInternAtom(display, "WM_DELETE_WINDOW", true);
+ net_wm_state = XInternAtom(display, "_NET_WM_STATE", false);
+ net_wm_ping = XInternAtom(display, "_NET_WM_PING", false);
+ net_wm_state_minimized = XInternAtom(display, "_NET_WM_STATE_MINIMIZED", false);
+ net_wm_state_fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", false);
+
+ xa_clipboard = XInternAtom(display, "CLIPBOARD", false);
+ xa_targets = XInternAtom(display, "TARGETS", false);
+ xa_utf8_string = XInternAtom(display, "UTF8_STRING", false);
+ xa_data_sel = XInternAtom(display, "CC_SEL_DATA", false);
+}
+
+static void RefreshWindowBounds(int width, int height) {
+ if (width != WindowInfo.Width || height != WindowInfo.Height) {
+ WindowInfo.Width = width;
+ WindowInfo.Height = height;
+ Event_RaiseVoid(&WindowEvents.Resized);
+ }
+}
+
+typedef int (*X11_ErrorHandler)(Display* dpy, XErrorEvent* ev);
+typedef int (*X11_IOErrorHandler)(Display* dpy);
+static X11_ErrorHandler realXErrorHandler;
+static X11_IOErrorHandler realXIOErrorHandler;
+
+static void LogXErrorCore(const char* msg) {
+ char traceBuffer[2048];
+ cc_string trace;
+ Platform_LogConst(msg);
+
+ String_InitArray(trace, traceBuffer);
+ Logger_Backtrace(&trace, NULL);
+ Platform_Log(traceBuffer, trace.length);
+}
+
+static int LogXError(Display* dpy, XErrorEvent* ev) {
+ LogXErrorCore("== unhandled X11 error ==");
+ return realXErrorHandler(dpy, ev);
+}
+
+static int LogXIOError(Display* dpy) {
+ LogXErrorCore("== unhandled XIO error ==");
+ return realXIOErrorHandler(dpy);
+}
+
+static void HookXErrors(void) {
+ realXErrorHandler = XSetErrorHandler(LogXError);
+ realXIOErrorHandler = XSetIOErrorHandler(LogXIOError);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------Public implementation--------------------------------------------------*
+*#########################################################################################################################*/
+#ifdef CC_BUILD_EGL
+static XVisualInfo GLContext_SelectVisual(void) {
+ XVisualInfo info;
+ cc_result res;
+ int screen = DefaultScreen(win_display);
+
+ res = XMatchVisualInfo(win_display, screen, 24, TrueColor, &info) ||
+ XMatchVisualInfo(win_display, screen, 32, TrueColor, &info);
+
+ if (!res) Logger_Abort("Selecting visual");
+ return info;
+}
+#else
+static XVisualInfo GLContext_SelectVisual(void);
+#endif
+
+void Window_Init(void) {
+ Display* display = XOpenDisplay(NULL);
+ int screen;
+
+ if (!display) Logger_Abort("Failed to open display");
+ screen = DefaultScreen(display);
+ HookXErrors();
+
+ win_display = display;
+ win_rootWin = RootWindow(display, screen);
+
+ /* TODO: Use Xinerama and XRandR for querying these */
+ DisplayInfo.Width = DisplayWidth(display, screen);
+ DisplayInfo.Height = DisplayHeight(display, screen);
+ DisplayInfo.Depth = DefaultDepth(display, screen);
+ DisplayInfo.ScaleX = 1;
+ DisplayInfo.ScaleY = 1;
+}
+
+#ifdef CC_BUILD_ICON
+extern const long CCIcon_Data[];
+extern const int CCIcon_Size;
+
+static void ApplyIcon(void) {
+ Atom net_wm_icon = XInternAtom(win_display, "_NET_WM_ICON", false);
+ Atom xa_cardinal = XInternAtom(win_display, "CARDINAL", false);
+ XChangeProperty(win_display, win_handle, net_wm_icon, xa_cardinal, 32, PropModeReplace, CCIcon_Data, CCIcon_Size);
+}
+#else
+static void ApplyIcon(void) { }
+#endif
+
+void Window_Create(int width, int height) {
+ XSetWindowAttributes attributes = { 0 };
+ XSizeHints hints = { 0 };
+ Atom protocols[2];
+ int supported, x, y;
+
+ x = Display_CentreX(width);
+ y = Display_CentreY(height);
+ RegisterAtoms();
+ win_visual = GLContext_SelectVisual();
+
+ Platform_LogConst("Opening render window... ");
+ attributes.colormap = XCreateColormap(win_display, win_rootWin, win_visual.visual, AllocNone);
+ attributes.event_mask = win_eventMask;
+
+ win_handle = XCreateWindow(win_display, win_rootWin, x, y, width, height,
+ 0, win_visual.depth /* CopyFromParent*/, InputOutput, win_visual.visual,
+ CWColormap | CWEventMask | CWBackPixel | CWBorderPixel, &attributes);
+ if (!win_handle) Logger_Abort("XCreateWindow failed");
+
+#ifdef CC_BUILD_XIM
+ win_xim = XOpenIM(win_display, NULL, NULL, NULL);
+ win_xic = XCreateIC(win_xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, win_handle, NULL);
+#endif
+
+ /* Set hints to try to force WM to create window at requested x,y */
+ /* Without this, some WMs will instead place the window whereever */
+ hints.base_width = width;
+ hints.base_height = height;
+ hints.flags = PSize | PPosition;
+ XSetWMNormalHints(win_display, win_handle, &hints);
+
+ /* Register for window destroy notification */
+ protocols[0] = wm_destroy;
+ protocols[1] = net_wm_ping;
+ XSetWMProtocols(win_display, win_handle, protocols, 2);
+
+ /* Request that auto-repeat is only set on devices that support it physically.
+ This typically means that it's turned off for keyboards (which is what we want).
+ We prefer this method over XAutoRepeatOff/On, because the latter needs to
+ be reset before the program exits. */
+ XkbSetDetectableAutoRepeat(win_display, true, &supported);
+
+ RefreshWindowBounds(width, height);
+ WindowInfo.Exists = true;
+ WindowInfo.Handle = (void*)win_handle;
+ grabCursor = Options_GetBool(OPT_GRAB_CURSOR, false);
+
+ /* So right name appears in e.g. Ubuntu Unity launchbar */
+ XClassHint hint = { 0 };
+ hint.res_name = GAME_APP_TITLE;
+ hint.res_class = GAME_APP_TITLE;
+ XSetClassHint(win_display, win_handle, &hint);
+ ApplyIcon();
+}
+
+void Window_SetTitle(const cc_string* title) {
+ char str[NATIVE_STR_LEN];
+ Platform_EncodeUtf8(str, title);
+ XStoreName(win_display, win_handle, str);
+}
+
+static char clipboard_copy_buffer[256];
+static char clipboard_paste_buffer[256];
+static cc_string clipboard_copy_text = String_FromArray(clipboard_copy_buffer);
+static cc_string clipboard_paste_text = String_FromArray(clipboard_paste_buffer);
+static cc_bool clipboard_paste_received;
+
+void Clipboard_GetText(cc_string* value) {
+ Window owner = XGetSelectionOwner(win_display, xa_clipboard);
+ int i;
+ if (!owner) return; /* no window owner */
+
+ XConvertSelection(win_display, xa_clipboard, xa_utf8_string, xa_data_sel, win_handle, 0);
+ clipboard_paste_received = false;
+ clipboard_paste_text.length = 0;
+
+ /* wait up to 1 second for SelectionNotify event to arrive */
+ for (i = 0; i < 100; i++) {
+ Window_ProcessEvents();
+ if (clipboard_paste_received) {
+ String_AppendString(value, &clipboard_paste_text);
+ return;
+ } else {
+ Thread_Sleep(10);
+ }
+ }
+}
+
+void Clipboard_SetText(const cc_string* value) {
+ String_Copy(&clipboard_copy_text, value);
+ XSetSelectionOwner(win_display, xa_clipboard, win_handle, 0);
+}
+
+void Window_Show(void) { XMapWindow(win_display, win_handle); }
+
+int Window_GetWindowState(void) {
+ cc_bool fullscreen = false, minimised = false;
+ Atom prop_type;
+ unsigned long items, after;
+ int i, prop_format;
+ Atom* data = NULL;
+
+ XGetWindowProperty(win_display, win_handle,
+ net_wm_state, 0, 256, false, xa_atom, &prop_type,
+ &prop_format, &items, &after, &data);
+
+ if (data) {
+ for (i = 0; i < items; i++) {
+ Atom atom = data[i];
+
+ if (atom == net_wm_state_minimized) {
+ minimised = true;
+ } else if (atom == net_wm_state_fullscreen) {
+ fullscreen = true;
+ }
+ }
+ XFree(data);
+ }
+
+ if (fullscreen) return WINDOW_STATE_FULLSCREEN;
+ if (minimised) return WINDOW_STATE_MINIMISED;
+ return WINDOW_STATE_NORMAL;
+}
+
+static void ToggleFullscreen(long op) {
+ XEvent ev = { 0 };
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = win_handle;
+ ev.xclient.message_type = net_wm_state;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = op;
+ ev.xclient.data.l[1] = net_wm_state_fullscreen;
+
+ XSendEvent(win_display, win_rootWin, false,
+ SubstructureRedirectMask | SubstructureNotifyMask, &ev);
+ XSync(win_display, false);
+ XRaiseWindow(win_display, win_handle);
+ Window_ProcessEvents();
+}
+
+cc_result Window_EnterFullscreen(void) {
+ ToggleFullscreen(_NET_WM_STATE_ADD); return 0;
+}
+cc_result Window_ExitFullscreen(void) {
+ ToggleFullscreen(_NET_WM_STATE_REMOVE); return 0;
+}
+
+void Window_SetSize(int width, int height) {
+ XResizeWindow(win_display, win_handle, width, height);
+ Window_ProcessEvents();
+}
+
+void Window_Close(void) {
+ XEvent ev = { 0 };
+ ev.type = ClientMessage;
+ ev.xclient.format = 32;
+ ev.xclient.display = win_display;
+ ev.xclient.window = win_handle;
+ ev.xclient.data.l[0] = wm_destroy;
+
+ XSendEvent(win_display, win_handle, false, 0, &ev);
+ XFlush(win_display);
+}
+
+static int MapNativeMouse(int button) {
+ if (button == 1) return KEY_LMOUSE;
+ if (button == 2) return KEY_MMOUSE;
+ if (button == 3) return KEY_RMOUSE;
+ if (button == 8) return KEY_XBUTTON1;
+ if (button == 9) return KEY_XBUTTON2;
+ return 0;
+}
+
+static int TryGetKey(XKeyEvent* ev) {
+ KeySym keysym1 = XLookupKeysym(ev, 0);
+ KeySym keysym2 = XLookupKeysym(ev, 1);
+
+ int key = MapNativeKey(keysym1, ev->state);
+ if (!key) key = MapNativeKey(keysym2, ev->state);
+ if (key) return key;
+
+ Platform_Log3("Unknown key %i (%x, %x)", &ev->keycode, &keysym1, &keysym2);
+ /* The user may be using a keyboard layout such as cryllic - */
+ /* fallback to trying to conver the raw scancodes instead */
+ return MapNativeKeycode(ev->keycode);
+}
+
+static Atom Window_GetSelectionProperty(XEvent* e) {
+ Atom prop = e->xselectionrequest.property;
+ if (prop) return prop;
+
+ /* For obsolete clients. See ICCCM spec, selections chapter for reasoning. */
+ return e->xselectionrequest.target;
+}
+
+static Bool FilterEvent(Display* d, XEvent* e, XPointer w) {
+ return
+ e->xany.window == (Window)w ||
+ !e->xany.window || /* KeymapNotify events don't have a window */
+ e->type == GenericEvent; /* For XInput events */
+}
+
+static void HandleWMDestroy(void) {
+ Platform_LogConst("Exit message received.");
+ Event_RaiseVoid(&WindowEvents.Closing);
+
+ /* sync and discard all events queued */
+ XSync(win_display, true);
+ XDestroyWindow(win_display, win_handle);
+ WindowInfo.Exists = false;
+}
+
+static void HandleWMPing(XEvent* e) {
+ e->xany.window = win_rootWin;
+ XSendEvent(win_display, win_rootWin, false,
+ SubstructureRedirectMask | SubstructureNotifyMask, e);
+}
+static void HandleGenericEvent(XEvent* e);
+
+void Window_ProcessEvents(void) {
+ XEvent e;
+ Window focus;
+ int focusRevert;
+ int i, btn, key, status;
+
+ while (WindowInfo.Exists) {
+ if (!XCheckIfEvent(win_display, &e, FilterEvent, (XPointer)win_handle)) break;
+ if (XFilterEvent(&e, None) == True) continue;
+
+ switch (e.type) {
+ case GenericEvent:
+ HandleGenericEvent(&e); break;
+ case ClientMessage:
+ if (e.xclient.data.l[0] == wm_destroy) {
+ HandleWMDestroy();
+ } else if (e.xclient.data.l[0] == net_wm_ping) {
+ HandleWMPing(&e);
+ }
+ break;
+
+ case DestroyNotify:
+ Platform_LogConst("Window destroyed");
+ WindowInfo.Exists = false;
+ break;
+
+ case ConfigureNotify:
+ RefreshWindowBounds(e.xconfigure.width, e.xconfigure.height);
+ break;
+
+ case Expose:
+ if (e.xexpose.count == 0) Event_RaiseVoid(&WindowEvents.Redraw);
+ break;
+
+ case LeaveNotify:
+ XGetInputFocus(win_display, &focus, &focusRevert);
+ if (focus == PointerRoot) {
+ WindowInfo.Focused = false; Event_RaiseVoid(&WindowEvents.FocusChanged);
+ }
+ break;
+
+ case EnterNotify:
+ XGetInputFocus(win_display, &focus, &focusRevert);
+ if (focus == PointerRoot) {
+ WindowInfo.Focused = true; Event_RaiseVoid(&WindowEvents.FocusChanged);
+ }
+ break;
+
+ case KeyPress:
+ {
+ char data[64], c;
+ key = TryGetKey(&e.xkey);
+ if (key) Input_SetPressed(key);
+
+#ifdef CC_BUILD_XIM
+ cc_codepoint cp;
+ char* chars = data;
+
+ status = Xutf8LookupString(win_xic, &e.xkey, data, Array_Elems(data), NULL, NULL);
+ for (; status > 0; status -= i) {
+ i = Convert_Utf8ToCodepoint(&cp, chars, status);
+ if (!i) break;
+
+ if (Convert_TryCodepointToCP437(cp, &c)) Event_RaiseInt(&InputEvents.Press, c);
+ chars += i;
+ }
+#else
+ /* This only really works for latin keys (e.g. so some finnish keys still work) */
+ status = XLookupString(&e.xkey, data, Array_Elems(data), NULL, NULL);
+ for (i = 0; i < status; i++) {
+ if (!Convert_TryCodepointToCP437((cc_uint8)data[i], &c)) continue;
+ Event_RaiseInt(&InputEvents.Press, c);
+ }
+#endif
+ } break;
+
+ case KeyRelease:
+ key = TryGetKey(&e.xkey);
+ if (key) Input_SetReleased(key);
+ break;
+
+ case ButtonPress:
+ btn = MapNativeMouse(e.xbutton.button);
+ if (btn) Input_SetPressed(btn);
+ else if (e.xbutton.button == 4) Mouse_ScrollWheel(+1);
+ else if (e.xbutton.button == 5) Mouse_ScrollWheel(-1);
+ break;
+
+ case ButtonRelease:
+ btn = MapNativeMouse(e.xbutton.button);
+ if (btn) Input_SetReleased(btn);
+ break;
+
+ case MotionNotify:
+ Pointer_SetPosition(0, e.xmotion.x, e.xmotion.y);
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ /* Don't lose focus when another app grabs key or mouse */
+ if (e.xfocus.mode == NotifyGrab || e.xfocus.mode == NotifyUngrab) break;
+
+ WindowInfo.Focused = e.type == FocusIn;
+ Event_RaiseVoid(&WindowEvents.FocusChanged);
+ /* TODO: Keep track of keyboard when focus is lost */
+ if (!WindowInfo.Focused) Input_Clear();
+ break;
+
+ case MappingNotify:
+ if (e.xmapping.request == MappingModifier || e.xmapping.request == MappingKeyboard) {
+ Platform_LogConst("keybard mapping refreshed");
+ XRefreshKeyboardMapping(&e.xmapping);
+ }
+ break;
+
+ case PropertyNotify:
+ if (e.xproperty.atom == net_wm_state) {
+ Event_RaiseVoid(&WindowEvents.StateChanged);
+ }
+ break;
+
+ case SelectionNotify:
+ if (e.xselection.selection == xa_clipboard && e.xselection.target == xa_utf8_string && e.xselection.property == xa_data_sel) {
+ Atom prop_type;
+ int prop_format;
+ unsigned long items, after;
+ cc_uint8* data = NULL;
+
+ XGetWindowProperty(win_display, win_handle, xa_data_sel, 0, 1024, false, 0,
+ &prop_type, &prop_format, &items, &after, &data);
+ XDeleteProperty(win_display, win_handle, xa_data_sel);
+
+ if (data && items && prop_type == xa_utf8_string) {
+ clipboard_paste_received = true;
+ clipboard_paste_text.length = 0;
+ String_AppendUtf8(&clipboard_paste_text, data, items);
+ }
+ if (data) XFree(data);
+ }
+ break;
+
+ case SelectionRequest:
+ {
+ XEvent reply = { 0 };
+ reply.xselection.type = SelectionNotify;
+ reply.xselection.send_event = true;
+ reply.xselection.display = win_display;
+ reply.xselection.requestor = e.xselectionrequest.requestor;
+ reply.xselection.selection = e.xselectionrequest.selection;
+ reply.xselection.target = e.xselectionrequest.target;
+ reply.xselection.property = 0;
+ reply.xselection.time = e.xselectionrequest.time;
+
+ if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_utf8_string && clipboard_copy_text.length) {
+ reply.xselection.property = Window_GetSelectionProperty(&e);
+ char str[800];
+ int len = Platform_EncodeUtf8(str, &clipboard_copy_text);
+
+ XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_utf8_string, 8,
+ PropModeReplace, (unsigned char*)str, len);
+ } else if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_targets) {
+ reply.xselection.property = Window_GetSelectionProperty(&e);
+
+ Atom data[2] = { xa_utf8_string, xa_targets };
+ XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_atom, 32,
+ PropModeReplace, (unsigned char*)data, 2);
+ }
+ XSendEvent(win_display, e.xselectionrequest.requestor, true, 0, &reply);
+ } break;
+ }
+ }
+}
+
+static void Cursor_GetRawPos(int* x, int* y) {
+ Window rootW, childW;
+ int childX, childY;
+ unsigned int mask;
+ XQueryPointer(win_display, win_rootWin, &rootW, &childW, x, y, &childX, &childY, &mask);
+}
+
+void Cursor_SetPosition(int x, int y) {
+ XWarpPointer(win_display, None, win_handle, 0, 0, 0, 0, x, y);
+ XFlush(win_display); /* TODO: not sure if XFlush call is necessary */
+}
+
+static Cursor blankCursor;
+static void Cursor_DoSetVisible(cc_bool visible) {
+ if (visible) {
+ XUndefineCursor(win_display, win_handle);
+ } else {
+ if (!blankCursor) {
+ char data = 0;
+ XColor col = { 0 };
+ Pixmap pixmap = XCreateBitmapFromData(win_display, win_handle, &data, 1, 1);
+ blankCursor = XCreatePixmapCursor(win_display, pixmap, pixmap, &col, &col, 0, 0);
+ XFreePixmap(win_display, pixmap);
+ }
+ XDefineCursor(win_display, win_handle, blankCursor);
+ }
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------X11 message box-----------------------------------------------------*
+*#########################################################################################################################*/
+struct X11MessageBox {
+ Window win;
+ Display* dpy;
+ GC gc;
+ unsigned long white, black, background;
+ unsigned long btnBorder, highlight, shadow;
+};
+
+static unsigned long X11_Col(struct X11MessageBox* m, cc_uint8 r, cc_uint8 g, cc_uint8 b) {
+ Colormap cmap = XDefaultColormap(m->dpy, DefaultScreen(m->dpy));
+ XColor col = { 0 };
+ col.red = r << 8;
+ col.green = g << 8;
+ col.blue = b << 8;
+ col.flags = DoRed | DoGreen | DoBlue;
+
+ XAllocColor(m->dpy, cmap, &col);
+ return col.pixel;
+}
+
+static void X11MessageBox_Init(struct X11MessageBox* m) {
+ m->black = BlackPixel(m->dpy, DefaultScreen(m->dpy));
+ m->white = WhitePixel(m->dpy, DefaultScreen(m->dpy));
+ m->background = X11_Col(m, 206, 206, 206);
+
+ m->btnBorder = X11_Col(m, 60, 60, 60);
+ m->highlight = X11_Col(m, 144, 144, 144);
+ m->shadow = X11_Col(m, 49, 49, 49);
+
+ m->win = XCreateSimpleWindow(m->dpy, DefaultRootWindow(m->dpy), 0, 0, 100, 100,
+ 0, m->black, m->background);
+ XSelectInput(m->dpy, m->win, ExposureMask | StructureNotifyMask |
+ KeyReleaseMask | PointerMotionMask |
+ ButtonPressMask | ButtonReleaseMask );
+
+ m->gc = XCreateGC(m->dpy, m->win, 0, NULL);
+ XSetForeground(m->dpy, m->gc, m->black);
+ XSetBackground(m->dpy, m->gc, m->background);
+}
+
+static void X11MessageBox_Free(struct X11MessageBox* m) {
+ XFreeGC(m->dpy, m->gc);
+ XDestroyWindow(m->dpy, m->win);
+}
+
+struct X11Textbox {
+ int x, y, width, height;
+ int lineHeight, descent;
+ const char* text;
+};
+
+static void X11Textbox_Measure(struct X11Textbox* t, XFontStruct* font) {
+ cc_string str = String_FromReadonly(t->text), line;
+ XCharStruct overall;
+ int direction, ascent, descent, lines = 0;
+
+ for (; str.length; lines++) {
+ String_UNSAFE_SplitBy(&str, '\n', &line);
+ XTextExtents(font, line.buffer, line.length, &direction, &ascent, &descent, &overall);
+ t->width = max(overall.width, t->width);
+ }
+
+ t->lineHeight = ascent + descent;
+ t->descent = descent;
+ t->height = t->lineHeight * lines;
+}
+
+static void X11Textbox_Draw(struct X11Textbox* t, struct X11MessageBox* m) {
+ cc_string str = String_FromReadonly(t->text), line;
+ int y = t->y + t->lineHeight - t->descent; /* TODO: is -descent even right? */
+
+ for (; str.length; y += t->lineHeight) {
+ String_UNSAFE_SplitBy(&str, '\n', &line);
+ XDrawString(m->dpy, m->win, m->gc, t->x, y, line.buffer, line.length);
+ }
+}
+
+struct X11Button {
+ int x, y, width, height;
+ cc_bool clicked;
+ struct X11Textbox text;
+};
+
+static void X11Button_Draw(struct X11Button* b, struct X11MessageBox* m) {
+ struct X11Textbox* t;
+ int begX, endX, begY, endY;
+
+ XSetForeground(m->dpy, m->gc, m->btnBorder);
+ XDrawRectangle(m->dpy, m->win, m->gc, b->x, b->y,
+ b->width, b->height);
+
+ t = &b->text;
+ begX = b->x + 1; endX = b->x + b->width - 1;
+ begY = b->y + 1; endY = b->y + b->height - 1;
+
+ if (b->clicked) {
+ XSetForeground(m->dpy, m->gc, m->highlight);
+ XDrawRectangle(m->dpy, m->win, m->gc, begX, begY,
+ endX - begX, endY - begY);
+ } else {
+ XSetForeground(m->dpy, m->gc, m->white);
+ XDrawLine(m->dpy, m->win, m->gc, begX, begY,
+ endX - 1, begY);
+ XDrawLine(m->dpy, m->win, m->gc, begX, begY,
+ begX, endY - 1);
+
+ XSetForeground(m->dpy, m->gc, m->highlight);
+ XDrawLine(m->dpy, m->win, m->gc, begX + 1, endY - 1,
+ endX - 1, endY - 1);
+ XDrawLine(m->dpy, m->win, m->gc, endX - 1, begY + 1,
+ endX - 1, endY - 1);
+
+ XSetForeground(m->dpy, m->gc, m->shadow);
+ XDrawLine(m->dpy, m->win, m->gc, begX, endY, endX, endY);
+ XDrawLine(m->dpy, m->win, m->gc, endX, begY, endX, endY);
+ }
+
+ XSetForeground(m->dpy, m->gc, m->black);
+ t->x = b->x + b->clicked + (b->width - t->width) / 2;
+ t->y = b->y + b->clicked + (b->height - t->height) / 2;
+ X11Textbox_Draw(t, m);
+}
+
+static int X11Button_Contains(struct X11Button* b, int x, int y) {
+ return x >= b->x && x < (b->x + b->width) &&
+ y >= b->y && y < (b->y + b->height);
+}
+
+static Bool X11_FilterEvent(Display* d, XEvent* e, XPointer w) { return e->xany.window == (Window)w; }
+static void X11_MessageBox(const char* title, const char* text, struct X11MessageBox* m) {
+ struct X11Button ok = { 0 };
+ struct X11Textbox body = { 0 };
+
+ Atom protocols[2];
+ XFontStruct* font;
+ int x, y, width, height;
+ XSizeHints hints = { 0 };
+ int mouseX = -1, mouseY = -1, over;
+ XEvent e;
+
+ X11MessageBox_Init(m);
+ XMapWindow(m->dpy, m->win);
+ XStoreName(m->dpy, m->win, title);
+
+ protocols[0] = XInternAtom(m->dpy, "WM_DELETE_WINDOW", false);
+ protocols[1] = XInternAtom(m->dpy, "_NET_WM_PING", false);
+ XSetWMProtocols(m->dpy, m->win, protocols, 2);
+
+ font = XQueryFont(m->dpy, XGContextFromGC(m->gc));
+ if (!font) return;
+
+ /* Compute size of widgets */
+ body.text = text;
+ X11Textbox_Measure(&body, font);
+ ok.text.text = "OK";
+ X11Textbox_Measure(&ok.text, font);
+ ok.width = ok.text.width + 70;
+ ok.height = ok.text.height + 10;
+
+ /* Compute size and position of window */
+ width = body.width + 20;
+ height = body.height + 20 + ok.height + 20;
+ x = DisplayWidth (m->dpy, DefaultScreen(m->dpy))/2 - width/2;
+ y = DisplayHeight(m->dpy, DefaultScreen(m->dpy))/2 - height/2;
+ XMoveResizeWindow(m->dpy, m->win, x, y, width, height);
+
+ /* Adjust bounds of widgets */
+ body.x = 10; body.y = 10;
+ ok.x = width/2 - ok.width/2;
+ ok.y = height - ok.height - 10;
+
+ /* This marks the window as popup window of the main window */
+ /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */
+ /* Depending on WM, removes minimise and doesn't show in taskbar */
+ if (win_handle) XSetTransientForHint(m->dpy, m->win, win_handle);
+
+ XFreeFontInfo(NULL, font, 1);
+ XUnmapWindow(m->dpy, m->win); /* Make window non resizeable */
+
+ hints.flags = PSize | PMinSize | PMaxSize;
+ hints.min_width = hints.max_width = hints.base_width = width;
+ hints.min_height = hints.max_height = hints.base_height = height;
+
+ XSetWMNormalHints(m->dpy, m->win, &hints);
+ XMapRaised(m->dpy, m->win);
+ XFlush(m->dpy);
+
+ for (;;) {
+ /* The naive solution is to use XNextEvent(m->dpy, &e) here. */
+ /* However this causes issues as that removes events that */
+ /* should have been delivered to the main game window. */
+ /* (e.g. breaks initial window resize with i3 WM) */
+ XIfEvent(m->dpy, &e, X11_FilterEvent, (XPointer)m->win);
+
+ switch (e.type)
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ if (e.xbutton.button != Button1) break;
+ over = X11Button_Contains(&ok, mouseX, mouseY);
+
+ if (ok.clicked && e.type == ButtonRelease) {
+ if (over) return;
+ }
+ ok.clicked = e.type == ButtonPress && over;
+ /* fallthrough to redraw window */
+
+ case Expose:
+ case MapNotify:
+ XClearWindow(m->dpy, m->win);
+ X11Textbox_Draw(&body, m);
+ X11Button_Draw(&ok, m);
+ XFlush(m->dpy);
+ break;
+
+ case KeyRelease:
+ if (XLookupKeysym(&e.xkey, 0) == XK_Escape) return;
+ break;
+
+ case ClientMessage:
+ /* { WM_DELETE_WINDOW, _NET_WM_PING } */
+ if (e.xclient.data.l[0] == protocols[0]) return;
+ if (e.xclient.data.l[0] == protocols[1]) HandleWMPing(&e);
+ break;
+
+ case MotionNotify:
+ mouseX = e.xmotion.x; mouseY = e.xmotion.y;
+ break;
+ }
+ }
+}
+
+static void ShowDialogCore(const char* title, const char* msg) {
+ struct X11MessageBox m = { 0 };
+ m.dpy = win_display;
+
+ /* Failing to create a display means can't display a message box. */
+ /* However the user might have launched the game through terminal, */
+ /* so fallback to console instead of just dying from a segfault */
+ if (!m.dpy) {
+ Platform_LogConst("### MESSAGE ###");
+ Platform_LogConst(title);
+ Platform_LogConst(msg);
+ return;
+ }
+
+ X11_MessageBox(title, msg, &m);
+ X11MessageBox_Free(&m);
+ XFlush(m.dpy); /* flush so window disappears immediately */
+}
+
+static GC fb_gc;
+static XImage* fb_image;
+void Window_AllocFramebuffer(struct Bitmap* bmp) {
+ if (!fb_gc) fb_gc = XCreateGC(win_display, win_handle, 0, NULL);
+
+ bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
+ fb_image = XCreateImage(win_display, win_visual.visual,
+ win_visual.depth, ZPixmap, 0, (char*)bmp->scan0,
+ bmp->width, bmp->height, 32, 0);
+}
+
+void Window_DrawFramebuffer(Rect2D r) {
+ XPutImage(win_display, win_handle, fb_gc, fb_image,
+ r.X, r.Y, r.X, r.Y, r.Width, r.Height);
+}
+
+void Window_FreeFramebuffer(struct Bitmap* bmp) {
+ XFree(fb_image);
+ Mem_Free(bmp->scan0);
+}
+
+void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
+void Window_SetKeyboardText(const cc_string* text) { }
+void Window_CloseKeyboard(void) { }
+
+static cc_bool rawMouseInited, rawMouseSupported;
+static int xiOpcode;
+
+static void CheckMovementDelta(double dx, double dy) {
+ /* Despite the assumption that XI_RawMotion is relative, */
+ /* unfortunately there's a few buggy corner cases out there */
+ /* where absolute coordinates are provided instead. */
+ /* The ugly code belows tries to detect these corner cases, */
+ /* and disables XInput2 when that happens */
+ static int valid, fails;
+
+ if (valid) return;
+ /* The default window resolution is 854 x 480, so if there's */
+ /* a delta less than half of that, then it's almost certain */
+ /* that the provided coordinates are relative.*/
+ if (dx < 300 || dy < 200) { valid = true; return; }
+
+ if (fails++ < 20) return;
+ /* Checked over 20 times now, but no relative coordinates, */
+ /* so give up trying to use XInput2 anymore. */
+ Platform_LogConst("Buggy XInput2 detected, disabling it..");
+ rawMouseSupported = false;
+}
+
+static void HandleGenericEvent(XEvent* e) {
+ const double* values;
+ XIRawEvent* ev;
+ double dx, dy;
+
+ if (!rawMouseSupported || e->xcookie.extension != xiOpcode) return;
+ if (!XGetEventData(win_display, &e->xcookie)) return;
+
+ if (e->xcookie.evtype == XI_RawMotion && Input_RawMode) {
+ ev = (XIRawEvent*)e->xcookie.data;
+ values = ev->raw_values;
+
+ /* Raw motion events may not always have values for both axes */
+ dx = XIMaskIsSet(ev->valuators.mask, 0) ? *values++ : 0;
+ dy = XIMaskIsSet(ev->valuators.mask, 1) ? *values++ : 0;
+
+ CheckMovementDelta(dx, dy);
+ /* Using 0.5f here makes the sensitivity about same as normal cursor movement */
+ Event_RaiseRawMove(&PointerEvents.RawMoved, dx * 0.5f, dy * 0.5f);
+ }
+ XFreeEventData(win_display, &e->xcookie);
+}
+
+static void InitRawMouse(void) {
+ XIEventMask evmask;
+ unsigned char masks[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ int ev, err, major, minor;
+
+ if (!XQueryExtension(win_display, "XInputExtension", &xiOpcode, &ev, &err)) {
+ Platform_LogConst("XInput unsupported");
+ return;
+ }
+
+ /* Only XInput 2.0 is actually required. However, 2.0 has the annoying */
+ /* behaviour where raw input is NOT delivered while pointer is grabbed. */
+ /* (i.e. if you press mouse button, no more raw mouse movement events) */
+ /* http://wine.1045685.n8.nabble.com/PATCH-0-9-Implement-DInput8-mouse-using-RawInput-and-XInput2-RawEvents-only-td6016923.html */
+ /* Thankfully XInput >= 2.1 corrects this behaviour */
+ /* http://who-t.blogspot.com/2011/09/whats-new-in-xi-21-raw-events.html */
+ major = 2; minor = 2;
+ if (XIQueryVersion(win_display, &major, &minor) != Success) {
+ Platform_Log2("Only XInput %i.%i supported", &major, &minor);
+ return;
+ }
+
+ /* Sometimes XIQueryVersion will report Success, even though the */
+ /* supported version is only 2.0! So make sure to handle that. */
+ if (major < 2 || minor < 2) {
+ Platform_Log2("Only XInput %i.%i supported", &major, &minor);
+ return;
+ }
+
+ XISetMask(masks, XI_RawMotion);
+ evmask.deviceid = XIAllMasterDevices;
+ evmask.mask_len = sizeof(masks);
+ evmask.mask = masks;
+
+ XISelectEvents(win_display, win_rootWin, &evmask, 1);
+ rawMouseSupported = true;
+}
+
+void Window_EnableRawMouse(void) {
+ DefaultEnableRawMouse();
+ if (!rawMouseInited) InitRawMouse();
+ rawMouseInited = true;
+
+ if (!grabCursor) return;
+ XGrabPointer(win_display, win_handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, win_handle, blankCursor, CurrentTime);
+}
+
+void Window_UpdateRawMouse(void) {
+ if (rawMouseSupported) {
+ /* Handled by XI_RawMotion generic event */
+ CentreMousePosition();
+ } else {
+ DefaultUpdateRawMouse();
+ }
+}
+
+void Window_DisableRawMouse(void) {
+ DefaultDisableRawMouse();
+ if (!grabCursor) return;
+ XUngrabPointer(win_display, CurrentTime);
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------glX OpenGL--------------------------------------------------------*
+*#########################################################################################################################*/
+#if defined CC_BUILD_GL && !defined CC_BUILD_EGL
+#include
+static GLXContext ctx_handle;
+typedef int (*FP_SWAPINTERVAL)(int interval);
+typedef Bool (*FP_QUERYRENDERER)(int attribute, unsigned int* value);
+static FP_SWAPINTERVAL swapIntervalMESA, swapIntervalSGI;
+static FP_QUERYRENDERER queryRendererMESA;
+
+void GLContext_Create(void) {
+ static const cc_string vsync_mesa = String_FromConst("GLX_MESA_swap_control");
+ static const cc_string vsync_sgi = String_FromConst("GLX_SGI_swap_control");
+ static const cc_string info_mesa = String_FromConst("GLX_MESA_query_renderer");
+
+ const char* raw_exts;
+ cc_string exts;
+ ctx_handle = glXCreateContext(win_display, &win_visual, NULL, true);
+
+ if (!ctx_handle) {
+ Platform_LogConst("Context create failed. Trying indirect...");
+ ctx_handle = glXCreateContext(win_display, &win_visual, NULL, false);
+ }
+ if (!ctx_handle) Logger_Abort("Failed to create OpenGL context");
+
+ if (!glXIsDirect(win_display, ctx_handle)) {
+ Platform_LogConst("== WARNING: Context is not direct ==");
+ }
+ if (!glXMakeCurrent(win_display, win_handle, ctx_handle)) {
+ Logger_Abort("Failed to make OpenGL context current.");
+ }
+
+ /* GLX may return non-null function pointers that don't actually work */
+ /* So we need to manually check the extensions string for support */
+ raw_exts = glXQueryExtensionsString(win_display, DefaultScreen(win_display));
+ exts = String_FromReadonly(raw_exts);
+
+ if (String_CaselessContains(&exts, &vsync_mesa)) {
+ swapIntervalMESA = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalMESA");
+ }
+ if (String_CaselessContains(&exts, &vsync_sgi)) {
+ swapIntervalSGI = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalSGI");
+ }
+ if (String_CaselessContains(&exts, &info_mesa)) {
+ queryRendererMESA = (FP_QUERYRENDERER)GLContext_GetAddress("glXQueryCurrentRendererIntegerMESA");
+ }
+}
+
+void GLContext_Update(void) { }
+cc_bool GLContext_TryRestore(void) { return true; }
+void GLContext_Free(void) {
+ if (!ctx_handle) return;
+ glXMakeCurrent(win_display, None, NULL);
+ glXDestroyContext(win_display, ctx_handle);
+ ctx_handle = NULL;
+}
+
+void* GLContext_GetAddress(const char* function) {
+ void* addr = (void*)glXGetProcAddress((const GLubyte*)function);
+ return GLContext_IsInvalidAddress(addr) ? NULL : addr;
+}
+
+cc_bool GLContext_SwapBuffers(void) {
+ glXSwapBuffers(win_display, win_handle);
+ return true;
+}
+
+void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+ int res = 0;
+ if (swapIntervalMESA) {
+ res = swapIntervalMESA(vsync);
+ } else if (swapIntervalSGI) {
+ res = swapIntervalSGI(vsync);
+ }
+ if (res) Platform_Log1("Set VSync failed, error: %i", &res);
+}
+
+void GLContext_GetApiInfo(cc_string* info) {
+ unsigned int vram, acc;
+ if (!queryRendererMESA) return;
+
+ queryRendererMESA(0x8186, &acc);
+ queryRendererMESA(0x8187, &vram);
+ String_Format2(info, "VRAM: %i MB, %c", &vram,
+ acc ? "HW accelerated" : "no HW acceleration");
+}
+
+static void GetAttribs(struct GraphicsMode* mode, int* attribs, int depth) {
+ int i = 0;
+ /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.opengl/doc/openglrf/glXChooseFBConfig.htm%23glxchoosefbconfig */
+ /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.opengl/doc/openglrf/glXChooseVisual.htm%23b5c84be452rree */
+ /* for the attribute declarations. Note that the attributes are different than those used in glxChooseVisual */
+
+ if (!mode->IsIndexed) { attribs[i++] = GLX_RGBA; }
+ attribs[i++] = GLX_RED_SIZE; attribs[i++] = mode->R;
+ attribs[i++] = GLX_GREEN_SIZE; attribs[i++] = mode->G;
+ attribs[i++] = GLX_BLUE_SIZE; attribs[i++] = mode->B;
+ attribs[i++] = GLX_ALPHA_SIZE; attribs[i++] = mode->A;
+ attribs[i++] = GLX_DEPTH_SIZE; attribs[i++] = depth;
+
+ attribs[i++] = GLX_DOUBLEBUFFER;
+ attribs[i++] = 0;
+}
+
+static XVisualInfo GLContext_SelectVisual(void) {
+ int attribs[20];
+ int major, minor;
+ XVisualInfo* visual = NULL;
+
+ int fbcount, screen;
+ GLXFBConfig* fbconfigs;
+ XVisualInfo info;
+ struct GraphicsMode mode;
+
+ InitGraphicsMode(&mode);
+ GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH);
+ screen = DefaultScreen(win_display);
+
+ if (!glXQueryVersion(win_display, &major, &minor)) {
+ Platform_LogConst("glXQueryVersion failed");
+ } else if (major >= 1 && minor >= 3) {
+ /* ChooseFBConfig returns an array of GLXFBConfig opaque structures */
+ fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount);
+ if (fbconfigs && fbcount) {
+ /* Use the first GLXFBConfig from the fbconfigs array (best match) */
+ visual = glXGetVisualFromFBConfig(win_display, *fbconfigs);
+ XFree(fbconfigs);
+ }
+ }
+
+ if (!visual) {
+ Platform_LogConst("Falling back to glXChooseVisual.");
+ visual = glXChooseVisual(win_display, screen, attribs);
+ }
+ /* Some really old devices will only supply 16 bit depths */
+ if (!visual) {
+ GetAttribs(&mode, attribs, 16);
+ visual = glXChooseVisual(win_display, screen, attribs);
+ }
+ if (!visual) Logger_Abort("Requested GraphicsMode not available.");
+
+ info = *visual;
+ XFree(visual);
+ return info;
+}
+#endif
+#endif
diff --git a/src/_WindowBase.h b/src/_WindowBase.h
index 8c3cfa88b..3b0c85c4f 100644
--- a/src/_WindowBase.h
+++ b/src/_WindowBase.h
@@ -2,6 +2,7 @@
#include "Input.h"
#include "Event.h"
#include "Logger.h"
+#include "Platform.h"
struct _DisplayData DisplayInfo;
struct _WinData WindowInfo;
@@ -9,18 +10,13 @@ struct _WinData WindowInfo;
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
-#if defined CC_BUILD_IOS
-/* iOS implements these functions in external interop_ios.m file */
-#define CC_MAYBE_OBJC1 extern
-#define CC_MAYBE_OBJC2 extern
-#define CC_OBJC_VISIBLE
-#elif defined CC_BUILD_COCOA
+#if defined CC_BUILD_COCOA
/* Cocoa implements some functions in external interop_cocoa.m file */
#define CC_MAYBE_OBJC1 extern
#define CC_MAYBE_OBJC2 static
#define CC_OBJC_VISIBLE
#else
-/* All other platforms implement internally in this file */
+/* All other platforms implement in the file including this .h file */
#define CC_MAYBE_OBJC1 static
#define CC_MAYBE_OBJC2 static
#define CC_OBJC_VISIBLE static
@@ -112,3 +108,107 @@ static void InitGraphicsMode(struct GraphicsMode* m) {
Logger_Abort2(bpp, "Unsupported bits per pixel"); break;
}
}
+
+#ifdef CC_BUILD_GL
+/* OpenGL contexts are heavily tied to the window, so for simplicitly are also included here */
+/* EGL is window system agnostic, other OpenGL context backends are tied to one windowing system. */
+#define GLContext_IsInvalidAddress(ptr) (ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr == (void*)2)
+
+void GLContext_GetAll(const struct DynamicLibSym* syms, int count) {
+ int i;
+ for (i = 0; i < count; i++) {
+ *syms[i].symAddr = GLContext_GetAddress(syms[i].name);
+ }
+}
+
+/*########################################################################################################################*
+*-------------------------------------------------------EGL OpenGL--------------------------------------------------------*
+*#########################################################################################################################*/
+#if defined CC_BUILD_EGL
+#include
+static EGLDisplay ctx_display;
+static EGLContext ctx_context;
+static EGLSurface ctx_surface;
+static EGLConfig ctx_config;
+static EGLint ctx_numConfig;
+
+static void GLContext_InitSurface(void) {
+ if (!win_handle) return; /* window not created or lost */
+ ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, win_handle, NULL);
+
+ if (!ctx_surface) return;
+ eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
+}
+
+static void GLContext_FreeSurface(void) {
+ if (!ctx_surface) return;
+ eglMakeCurrent(ctx_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(ctx_display, ctx_surface);
+ ctx_surface = NULL;
+}
+
+void GLContext_Create(void) {
+ static EGLint contextAttribs[3] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+ static EGLint attribs[19] = {
+ EGL_RED_SIZE, 0, EGL_GREEN_SIZE, 0,
+ EGL_BLUE_SIZE, 0, EGL_ALPHA_SIZE, 0,
+ EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH,
+ EGL_STENCIL_SIZE, 0,
+ EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+
+ struct GraphicsMode mode;
+ InitGraphicsMode(&mode);
+ attribs[1] = mode.R; attribs[3] = mode.G;
+ attribs[5] = mode.B; attribs[7] = mode.A;
+
+ ctx_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(ctx_display, NULL, NULL);
+ eglBindAPI(EGL_OPENGL_ES_API);
+ eglChooseConfig(ctx_display, attribs, &ctx_config, 1, &ctx_numConfig);
+
+ ctx_context = eglCreateContext(ctx_display, ctx_config, EGL_NO_CONTEXT, contextAttribs);
+ GLContext_InitSurface();
+}
+
+void GLContext_Update(void) {
+ GLContext_FreeSurface();
+ GLContext_InitSurface();
+}
+
+cc_bool GLContext_TryRestore(void) {
+ GLContext_FreeSurface();
+ GLContext_InitSurface();
+ return ctx_surface != NULL;
+}
+
+void GLContext_Free(void) {
+ GLContext_FreeSurface();
+ eglDestroyContext(ctx_display, ctx_context);
+ eglTerminate(ctx_display);
+}
+
+void* GLContext_GetAddress(const char* function) {
+ return eglGetProcAddress(function);
+}
+
+cc_bool GLContext_SwapBuffers(void) {
+ EGLint err;
+ if (!ctx_surface) return false;
+ if (eglSwapBuffers(ctx_display, ctx_surface)) return true;
+
+ err = eglGetError();
+ /* TODO: figure out what errors need to be handled here */
+ Logger_Abort2(err, "Failed to swap buffers");
+ return false;
+}
+
+void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+ eglSwapInterval(ctx_display, vsync);
+}
+void GLContext_GetApiInfo(cc_string* info) { }
+#endif
+#endif
diff --git a/src/interop_ios.m b/src/interop_ios.m
index 8147506f3..40baf7f77 100644
--- a/src/interop_ios.m
+++ b/src/interop_ios.m
@@ -1,4 +1,4 @@
-#include "Window.h"
+#include "_WindowBase.h"
#include "Platform.h"
#include "String.h"
#include "Errors.h"