Bug Summary

File:Window.c
Warning:line 1733, column 25
The left operand of '+' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -triple amd64-unknown-openbsd6.8 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name Window.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mthread-model posix -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -masm-verbose -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -dwarf-column-info -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/local/lib/clang/10.0.1 -I /usr/X11R6/include -I /usr/local/include -fdebug-compilation-dir /home/ben/Projects/ClassiCube/src -ferror-limit 19 -fmessage-length 0 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -fobjc-runtime=gnustep -fdiagnostics-show-option -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -o /home/ben/Projects/ClassiCube/src/scan/2021-01-09-173753-31878-1 -x c Window.c
1#include "Window.h"
2#include "String.h"
3#include "Platform.h"
4#include "Input.h"
5#include "Event.h"
6#include "Logger.h"
7#include "Funcs.h"
8#include "ExtMath.h"
9#include "Bitmap.h"
10#include "Options.h"
11#include "Errors.h"
12
13struct _DisplayData DisplayInfo;
14struct _WinData WindowInfo;
15
16int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
17int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
18#define Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2) (DisplayInfo.X + (DisplayInfo.Width - width) / 2)
19#define Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2)
20
21#ifndef CC_BUILD_WEB
22void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
23 cc_string text; char textBuffer[2048];
24 String_InitArray(text, textBuffer)text.buffer = textBuffer; text.length = 0; text.capacity = sizeof
(textBuffer);
;
25
26 Clipboard_GetText(&text);
27 callback(&text, obj);
28}
29#endif
30
31static int cursorPrevX, cursorPrevY;
32static cc_bool cursorVisible = true1;
33/* Gets the position of the cursor in screen or window coordinates. */
34static void Cursor_GetRawPos(int* x, int* y);
35static void Cursor_DoSetVisible(cc_bool visible);
36
37void Cursor_SetVisible(cc_bool visible) {
38 if (cursorVisible == visible) return;
39 cursorVisible = visible;
40 Cursor_DoSetVisible(visible);
41}
42
43static void CentreMousePosition(void) {
44 Cursor_SetPosition(WindowInfo.Width / 2, WindowInfo.Height / 2);
45 /* Fixes issues with large DPI displays on Windows >= 8.0. */
46 Cursor_GetRawPos(&cursorPrevX, &cursorPrevY);
47}
48
49static void RegrabMouse(void) {
50 if (!WindowInfo.Focused || !WindowInfo.Exists) return;
51 CentreMousePosition();
52}
53
54static void DefaultEnableRawMouse(void) {
55 Input_RawMode = true1;
56 RegrabMouse();
57 Cursor_SetVisible(false0);
58}
59
60static void DefaultUpdateRawMouse(void) {
61 int x, y;
62 Cursor_GetRawPos(&x, &y);
63 Event_RaiseRawMove(&PointerEvents.RawMoved, x - cursorPrevX, y - cursorPrevY);
64 CentreMousePosition();
65}
66
67static void DefaultDisableRawMouse(void) {
68 Input_RawMode = false0;
69 RegrabMouse();
70 Cursor_SetVisible(true1);
71}
72
73/* The actual windowing system specific method to display a message box */
74static void ShowDialogCore(const char* title, const char* msg);
75void Window_ShowDialog(const char* title, const char* msg) {
76 /* Ensure cursor is visible while showing message box */
77 cc_bool visible = cursorVisible;
78
79 if (!visible) Cursor_SetVisible(true1);
1
Assuming 'visible' is not equal to 0
2
Taking false branch
80 ShowDialogCore(title, msg);
3
Calling 'ShowDialogCore'
81 if (!visible) Cursor_SetVisible(false0);
82}
83
84void OpenKeyboardArgs_Init(struct OpenKeyboardArgs* args, STRING_REF const cc_string* text, int type) {
85 args->text = text;
86 args->type = type;
87 args->placeholder = "";
88}
89
90
91struct GraphicsMode { int R, G, B, A, IsIndexed; };
92/* Creates a GraphicsMode compatible with the default display device */
93static void InitGraphicsMode(struct GraphicsMode* m) {
94 int bpp = DisplayInfo.Depth;
95 m->IsIndexed = bpp < 15;
96
97 m->A = 0;
98 switch (bpp) {
99 case 32:
100 m->R = 8; m->G = 8; m->B = 8; m->A = 8; break;
101 case 24:
102 m->R = 8; m->G = 8; m->B = 8; break;
103 case 16:
104 m->R = 5; m->G = 6; m->B = 5; break;
105 case 15:
106 m->R = 5; m->G = 5; m->B = 5; break;
107 case 8:
108 m->R = 3; m->G = 3; m->B = 2; break;
109 case 4:
110 m->R = 2; m->G = 2; m->B = 1; break;
111 default:
112 /* mode->R = 0; mode->G = 0; mode->B = 0; */
113 Logger_Abort2(bpp, "Unsupported bits per pixel"); break;
114 }
115}
116
117
118/*########################################################################################################################*
119*-------------------------------------------------------SDL window--------------------------------------------------------*
120*#########################################################################################################################*/
121#if defined CC_BUILD_SDL
122#include <SDL2/SDL.h>
123#include "Graphics.h"
124static SDL_Window* win_handle;
125
126static void RefreshWindowBounds(void) {
127 SDL_GetWindowSize(win_handle, &WindowInfo.Width, &WindowInfo.Height);
128}
129
130static void Window_SDLFail(const char* place) {
131 char strBuffer[256];
132 cc_string str;
133 String_InitArray_NT(str, strBuffer)str.buffer = strBuffer; str.length = 0; str.capacity = sizeof
(strBuffer) - 1;
;
134
135 String_Format2(&str, "Error when %c: %c", place, SDL_GetError());
136 str.buffer[str.length] = '\0';
137 Logger_Abort(str.buffer);
138}
139
140void Window_Init(void) {
141 SDL_DisplayMode mode = { 0 };
142 SDL_Init(SDL_INIT_VIDEO);
143 SDL_GetDesktopDisplayMode(0, &mode);
144
145 DisplayInfo.Width = mode.w;
146 DisplayInfo.Height = mode.h;
147 DisplayInfo.Depth = SDL_BITSPERPIXEL(mode.format);
148 DisplayInfo.ScaleX = 1;
149 DisplayInfo.ScaleY = 1;
150}
151
152void Window_Create(int width, int height) {
153 int x = Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2);
154 int y = Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2);
155
156 /* TODO: Don't set this flag for launcher window */
157 win_handle = SDL_CreateWindow(NULL((void*)0), x, y, width, height, SDL_WINDOW_OPENGL);
158 if (!win_handle) Window_SDLFail("creating window");
159
160 RefreshWindowBounds();
161 WindowInfo.Exists = true1;
162 WindowInfo.Handle = win_handle;
163}
164
165void Window_SetTitle(const cc_string* title) {
166 char str[NATIVE_STR_LEN600];
167 Platform_EncodeUtf8(str, title);
168 SDL_SetWindowTitle(win_handle, str);
169}
170
171void Clipboard_GetText(cc_string* value) {
172 char* ptr = SDL_GetClipboardText();
173 if (!ptr) return;
174
175 int len = String_Length(ptr);
176 String_AppendUtf8(value, ptr, len);
177 SDL_free(ptr);
178}
179
180void Clipboard_SetText(const cc_string* value) {
181 char str[NATIVE_STR_LEN600];
182 Platform_EncodeUtf8(str, value);
183 SDL_SetClipboardText(str);
184}
185
186void Window_Show(void) { SDL_ShowWindow(win_handle); }
187
188int Window_GetWindowState(void) {
189 Uint32 flags = SDL_GetWindowFlags(win_handle);
190
191 if (flags & SDL_WINDOW_MINIMIZED) return WINDOW_STATE_MINIMISED;
192 if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) return WINDOW_STATE_FULLSCREEN;
193 return WINDOW_STATE_NORMAL;
194}
195
196cc_result Window_EnterFullscreen(void) {
197 return SDL_SetWindowFullscreen(win_handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
198}
199cc_result Window_ExitFullscreen(void) { SDL_RestoreWindow(win_handle); return 0; }
200
201void Window_SetSize(int width, int height) {
202 SDL_SetWindowSize(win_handle, width, height);
203}
204
205void Window_Close(void) {
206 SDL_Event e;
207 e.type = SDL_QUIT;
208 SDL_PushEvent(&e);
209}
210
211static int MapNativeKey(SDL_Keycode k) {
212 if (k >= SDLK_0 && k <= SDLK_9) { return '0' + (k - SDLK_0); }
213 if (k >= SDLK_a && k <= SDLK_z) { return 'A' + (k - SDLK_a); }
214 if (k >= SDLK_F1 && k <= SDLK_F12) { return KEY_F1 + (k - SDLK_F1); }
215 if (k >= SDLK_F13 && k <= SDLK_F24) { return KEY_F13 + (k - SDLK_F13); }
216 /* SDLK_KP_0 isn't before SDLK_KP_1 */
217 if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return KEY_KP1 + (k - SDLK_KP_1); }
218
219 switch (k) {
220 case SDLK_RETURN: return KEY_ENTER;
221 case SDLK_ESCAPE: return KEY_ESCAPE;
222 case SDLK_BACKSPACE: return KEY_BACKSPACE;
223 case SDLK_TAB: return KEY_TAB;
224 case SDLK_SPACE: return KEY_SPACE;
225 case SDLK_QUOTE: return KEY_QUOTE;
226 case SDLK_EQUALS: return KEY_EQUALS;
227 case SDLK_COMMA: return KEY_COMMA;
228 case SDLK_MINUS: return KEY_MINUS;
229 case SDLK_PERIOD: return KEY_PERIOD;
230 case SDLK_SLASH: return KEY_SLASH;
231 case SDLK_SEMICOLON: return KEY_SEMICOLON;
232 case SDLK_LEFTBRACKET: return KEY_LBRACKET;
233 case SDLK_BACKSLASH: return KEY_BACKSLASH;
234 case SDLK_RIGHTBRACKET: return KEY_RBRACKET;
235 case SDLK_BACKQUOTE: return KEY_TILDE;
236 case SDLK_CAPSLOCK: return KEY_CAPSLOCK;
237 case SDLK_PRINTSCREEN: return KEY_PRINTSCREEN;
238 case SDLK_SCROLLLOCK: return KEY_SCROLLLOCK;
239 case SDLK_PAUSE: return KEY_PAUSE;
240 case SDLK_INSERT: return KEY_INSERT;
241 case SDLK_HOME: return KEY_HOME;
242 case SDLK_PAGEUP: return KEY_PAGEUP;
243 case SDLK_DELETE: return KEY_DELETE;
244 case SDLK_END: return KEY_END;
245 case SDLK_PAGEDOWN: return KEY_PAGEDOWN;
246 case SDLK_RIGHT: return KEY_RIGHT;
247 case SDLK_LEFT: return KEY_LEFT;
248 case SDLK_DOWN: return KEY_DOWN;
249 case SDLK_UP: return KEY_UP;
250
251 case SDLK_NUMLOCKCLEAR: return KEY_NUMLOCK;
252 case SDLK_KP_DIVIDE: return KEY_KP_DIVIDE;
253 case SDLK_KP_MULTIPLY: return KEY_KP_MULTIPLY;
254 case SDLK_KP_MINUS: return KEY_KP_MINUS;
255 case SDLK_KP_PLUS: return KEY_KP_PLUS;
256 case SDLK_KP_ENTER: return KEY_KP_ENTER;
257 case SDLK_KP_0: return KEY_KP0;
258 case SDLK_KP_PERIOD: return KEY_KP_DECIMAL;
259
260 case SDLK_LCTRL: return KEY_LCTRL;
261 case SDLK_LSHIFT: return KEY_LSHIFT;
262 case SDLK_LALT: return KEY_LALT;
263 case SDLK_LGUI: return KEY_LWIN;
264 case SDLK_RCTRL: return KEY_RCTRL;
265 case SDLK_RSHIFT: return KEY_RSHIFT;
266 case SDLK_RALT: return KEY_RALT;
267 case SDLK_RGUI: return KEY_RWIN;
268 }
269 return KEY_NONE;
270}
271
272static void OnKeyEvent(const SDL_Event* e) {
273 cc_bool pressed = e->key.state == SDL_PRESSED;
274 int key = MapNativeKey(e->key.keysym.sym);
275 if (key) Input_Set(key, pressed);
276}
277
278static void OnMouseEvent(const SDL_Event* e) {
279 cc_bool pressed = e->button.state == SDL_PRESSED;
280 int btn;
281 switch (e->button.button) {
282 case SDL_BUTTON_LEFT: btn = KEY_LMOUSE; break;
283 case SDL_BUTTON_MIDDLE: btn = KEY_MMOUSE; break;
284 case SDL_BUTTON_RIGHT: btn = KEY_RMOUSE; break;
285 case SDL_BUTTON_X1: btn = KEY_XBUTTON1; break;
286 case SDL_BUTTON_X2: btn = KEY_XBUTTON2; break;
287 default: return;
288 }
289 Input_Set(btn, pressed);
290}
291
292static void OnTextEvent(const SDL_Event* e) {
293 char buffer[SDL_TEXTINPUTEVENT_TEXT_SIZE];
294 cc_string str;
295 int i, len;
296
297 String_InitArray(str, buffer)str.buffer = buffer; str.length = 0; str.capacity = sizeof(buffer
);
;
298 len = String_CalcLen(e->text.text, SDL_TEXTINPUTEVENT_TEXT_SIZE);
299 String_AppendUtf8(&str, e->text.text, len);
300
301 for (i = 0; i < str.length; i++) {
302 Event_RaiseInt(&InputEvents.Press, str.buffer[i]);
303 }
304}
305
306static void OnWindowEvent(const SDL_Event* e) {
307 switch (e->window.event) {
308 case SDL_WINDOWEVENT_EXPOSED:
309 Event_RaiseVoid(&WindowEvents.Redraw);
310 break;
311 case SDL_WINDOWEVENT_SIZE_CHANGED:
312 RefreshWindowBounds();
313 Event_RaiseVoid(&WindowEvents.Resized);
314 break;
315 case SDL_WINDOWEVENT_MINIMIZED:
316 case SDL_WINDOWEVENT_MAXIMIZED:
317 case SDL_WINDOWEVENT_RESTORED:
318 Event_RaiseVoid(&WindowEvents.StateChanged);
319 break;
320 case SDL_WINDOWEVENT_FOCUS_GAINED:
321 WindowInfo.Focused = true1;
322 Event_RaiseVoid(&WindowEvents.FocusChanged);
323 break;
324 case SDL_WINDOWEVENT_FOCUS_LOST:
325 WindowInfo.Focused = false0;
326 Event_RaiseVoid(&WindowEvents.FocusChanged);
327 break;
328 case SDL_WINDOWEVENT_CLOSE:
329 Window_Close();
330 break;
331 }
332}
333
334void Window_ProcessEvents(void) {
335 SDL_Event e;
336 while (SDL_PollEvent(&e)) {
337 switch (e.type) {
338
339 case SDL_KEYDOWN:
340 case SDL_KEYUP:
341 OnKeyEvent(&e); break;
342 case SDL_MOUSEBUTTONDOWN:
343 case SDL_MOUSEBUTTONUP:
344 OnMouseEvent(&e); break;
345 case SDL_MOUSEWHEEL:
346 Mouse_ScrollWheel(e.wheel.y);
347 break;
348 case SDL_MOUSEMOTION:
349 Pointer_SetPosition(0, e.motion.x, e.motion.y);
350 if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel);
351 break;
352 case SDL_TEXTINPUT:
353 OnTextEvent(&e); break;
354 case SDL_WINDOWEVENT:
355 OnWindowEvent(&e); break;
356
357 case SDL_QUIT:
358 WindowInfo.Exists = false0;
359 Event_RaiseVoid(&WindowEvents.Closing);
360 SDL_DestroyWindow(win_handle);
361 break;
362
363 case SDL_RENDER_DEVICE_RESET:
364 Gfx_LoseContext("SDL device reset event");
365 Gfx_RecreateContext();
366 break;
367 }
368 }
369}
370
371static void Cursor_GetRawPos(int* x, int* y) {
372 SDL_GetMouseState(x, y);
373}
374void Cursor_SetPosition(int x, int y) {
375 SDL_WarpMouseInWindow(win_handle, x, y);
376}
377
378static void Cursor_DoSetVisible(cc_bool visible) {
379 SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
380}
381
382static void ShowDialogCore(const char* title, const char* msg) {
383 SDL_ShowSimpleMessageBox(0, title, msg, win_handle);
384}
385
386static SDL_Surface* surface;
387void Window_AllocFramebuffer(struct Bitmap* bmp) {
388 surface = SDL_GetWindowSurface(win_handle);
389 if (!surface) Window_SDLFail("getting window surface");
390
391 if (SDL_MUSTLOCK(surface)) {
392 int ret = SDL_LockSurface(surface);
393 if (ret < 0) Window_SDLFail("locking window surface");
394 }
395 bmp->scan0 = surface->pixels;
396}
397
398void Window_DrawFramebuffer(Rect2D r) {
399 SDL_Rect rect;
400 rect.x = r.X; rect.w = r.Width;
401 rect.y = r.Y; rect.h = r.Height;
402 SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1);
403}
404
405void Window_FreeFramebuffer(struct Bitmap* bmp) {
406 /* SDL docs explicitly say to NOT free the surface */
407 /* https://wiki.libsdl.org/SDL_GetWindowSurface */
408 /* TODO: Do we still need to unlock it though? */
409}
410
411void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { SDL_StartTextInput(); }
412void Window_SetKeyboardText(const cc_string* text) { }
413void Window_CloseKeyboard(void) { SDL_StopTextInput(); }
414
415void Window_EnableRawMouse(void) {
416 RegrabMouse();
417 SDL_SetRelativeMouseMode(true1);
418 Input_RawMode = true1;
419}
420void Window_UpdateRawMouse(void) { CentreMousePosition(); }
421
422void Window_DisableRawMouse(void) {
423 RegrabMouse();
424 SDL_SetRelativeMouseMode(false0);
425 Input_RawMode = false0;
426}
427
428
429/*########################################################################################################################*
430*------------------------------------------------------Win32 window-------------------------------------------------------*
431*#########################################################################################################################*/
432#elif defined CC_BUILD_WINGUI
433#define WIN32_LEAN_AND_MEAN
434#define NOSERVICE
435#define NOMCX
436#define NOIME
437#ifndef UNICODE
438#define UNICODE
439#define _UNICODE
440#endif
441
442#ifndef _WIN32_WINNT
443#define _WIN32_WINNT 0x0501 /* Windows XP */
444/* NOTE: Functions that are not present on Windows 2000 are dynamically loaded. */
445/* Hence the actual minimum supported OS is Windows 2000. This just avoids redeclaring structs. */
446#endif
447#include <windows.h>
448
449#define CC_WIN_STYLE WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
450#define CC_WIN_CLASSNAME TEXT("ClassiCube_Window")
451#define Rect_Width(rect) (rect.right - rect.left)
452#define Rect_Height(rect) (rect.bottom - rect.top)
453
454#ifndef WM_XBUTTONDOWN
455/* Missing if _WIN32_WINNT isn't defined */
456#define WM_XBUTTONDOWN 0x020B
457#define WM_XBUTTONUP 0x020C
458#endif
459
460typedef BOOL (WINAPI *FUNC_RegisterRawInput)(PCRAWINPUTDEVICE devices, UINT numDevices, UINT size);
461static FUNC_RegisterRawInput _registerRawInput;
462typedef UINT (WINAPI *FUNC_GetRawInputData)(HRAWINPUT hRawInput, UINT cmd, void* data, UINT* size, UINT headerSize);
463static FUNC_GetRawInputData _getRawInputData;
464
465static HINSTANCE win_instance;
466static HWND win_handle;
467static HDC win_DC;
468static cc_bool suppress_resize;
469static int win_totalWidth, win_totalHeight; /* Size of window including titlebar and borders */
470static int windowX, windowY;
471static cc_bool is_ansiWindow;
472
473static const cc_uint8 key_map[14 * 16] = {
474 0, 0, 0, 0, 0, 0, 0, 0, KEY_BACKSPACE, KEY_TAB, 0, 0, 0, KEY_ENTER, 0, 0,
475 0, 0, 0, KEY_PAUSE, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, 0, 0, 0, 0,
476 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,
477 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
478 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
479 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', KEY_LWIN, KEY_RWIN, KEY_MENU, 0, 0,
480 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,
481 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,
482 KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, 0, 0, 0, 0, 0, 0, 0, 0,
483 KEY_NUMLOCK, KEY_SCROLLLOCK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
484 KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
485 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_SEMICOLON, KEY_EQUALS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH,
486 KEY_TILDE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
487 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_LBRACKET, KEY_BACKSLASH, KEY_RBRACKET, KEY_QUOTE, 0,
488};
489static int MapNativeKey(WPARAM key, LPARAM meta) {
490 LPARAM ext = meta & (1UL << 24);
491 switch (key)
492 {
493 case VK_CONTROL:
494 return ext ? KEY_RCTRL : KEY_LCTRL;
495 case VK_MENU:
496 return ext ? KEY_RALT : KEY_LALT;
497 case VK_RETURN:
498 return ext ? KEY_KP_ENTER : KEY_ENTER;
499 default:
500 return key < Array_Elems(key_map)(sizeof(key_map) / sizeof(key_map[0])) ? key_map[key] : 0;
501 }
502}
503
504static void RefreshWindowBounds(void) {
505 RECT rect;
506 POINT topLeft = { 0, 0 };
507
508 GetWindowRect(win_handle, &rect);
509 win_totalWidth = Rect_Width(rect);
510 win_totalHeight = Rect_Height(rect);
511
512 GetClientRect(win_handle, &rect);
513 WindowInfo.Width = Rect_Width(rect);
514 WindowInfo.Height = Rect_Height(rect);
515
516 /* GetClientRect always returns 0,0 for left,top (see MSDN) */
517 ClientToScreen(win_handle, &topLeft);
518 windowX = topLeft.x; windowY = topLeft.y;
519}
520
521static LRESULT CALLBACK Window_Procedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
522 char keyChar;
523 float wheelDelta;
524
525 switch (message) {
526 case WM_ACTIVATE:
527 WindowInfo.Focused = LOWORD(wParam) != 0;
528 Event_RaiseVoid(&WindowEvents.FocusChanged);
529 break;
530
531 case WM_ERASEBKGND:
532 return 1; /* Avoid flickering */
533
534 case WM_PAINT:
535 ValidateRect(win_handle, NULL((void*)0));
536 Event_RaiseVoid(&WindowEvents.Redraw);
537 return 0;
538
539 case WM_WINDOWPOSCHANGED:
540 {
541 WINDOWPOS* pos = (WINDOWPOS*)lParam;
542 if (pos->hwnd != win_handle) break;
543 cc_bool sized = pos->cx != win_totalWidth || pos->cy != win_totalHeight;
544
545 RefreshWindowBounds();
546 if (sized && !suppress_resize) Event_RaiseVoid(&WindowEvents.Resized);
547 } break;
548
549 case WM_SIZE:
550 Event_RaiseVoid(&WindowEvents.StateChanged);
551 break;
552
553 case WM_CHAR:
554 /* TODO: Use WM_UNICHAR instead, as WM_CHAR is just utf16 */
555 if (Convert_TryCodepointToCP437((cc_unichar)wParam, &keyChar)) {
556 Event_RaiseInt(&InputEvents.Press, keyChar);
557 }
558 break;
559
560 case WM_MOUSEMOVE:
561 /* Set before position change, in case mouse buttons changed when outside window */
562 Input_SetNonRepeatable(KEY_LMOUSE, wParam & 0x01);
563 Input_SetNonRepeatable(KEY_RMOUSE, wParam & 0x02);
564 Input_SetNonRepeatable(KEY_MMOUSE, wParam & 0x10);
565 /* TODO: do we need to set XBUTTON1/XBUTTON2 here */
566 Pointer_SetPosition(0, LOWORD(lParam), HIWORD(lParam));
567 break;
568
569 case WM_MOUSEWHEEL:
570 wheelDelta = ((short)HIWORD(wParam)) / (float)WHEEL_DELTA;
571 Mouse_ScrollWheel(wheelDelta);
572 return 0;
573
574 case WM_LBUTTONDOWN:
575 Input_SetPressed(KEY_LMOUSE); break;
576 case WM_MBUTTONDOWN:
577 Input_SetPressed(KEY_MMOUSE); break;
578 case WM_RBUTTONDOWN:
579 Input_SetPressed(KEY_RMOUSE); break;
580 case WM_XBUTTONDOWN:
581 Input_SetPressed(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
582 break;
583
584 case WM_LBUTTONUP:
585 Input_SetReleased(KEY_LMOUSE); break;
586 case WM_MBUTTONUP:
587 Input_SetReleased(KEY_MMOUSE); break;
588 case WM_RBUTTONUP:
589 Input_SetReleased(KEY_RMOUSE); break;
590 case WM_XBUTTONUP:
591 Input_SetReleased(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
592 break;
593
594 case WM_INPUT:
595 {
596 RAWINPUT raw;
597 UINT ret, rawSize = sizeof(RAWINPUT);
598 int dx, dy;
599
600 ret = _getRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
601 if (ret == -1 || raw.header.dwType != RIM_TYPEMOUSE) break;
602
603 if (raw.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
604 dx = raw.data.mouse.lLastX;
605 dy = raw.data.mouse.lLastY;
606 } else if (raw.data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
607 static int prevPosX, prevPosY;
608 dx = raw.data.mouse.lLastX - prevPosX;
609 dy = raw.data.mouse.lLastY - prevPosY;
610
611 prevPosX = raw.data.mouse.lLastX;
612 prevPosY = raw.data.mouse.lLastY;
613 } else { break; }
614
615 if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, (float)dx, (float)dy);
616 } break;
617
618 case WM_KEYDOWN:
619 case WM_KEYUP:
620 case WM_SYSKEYDOWN:
621 case WM_SYSKEYUP:
622 {
623 cc_bool pressed = message == WM_KEYDOWN || message == WM_SYSKEYDOWN;
624 /* Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed
625 and released. It looks like neither key is released in this case, or that the wrong key is
626 released in the case of Control and Alt.
627 To combat this, we are going to release both keys when either is released. Hacky, but should work.
628 Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0).
629 In this case, both keys will be reported as pressed. */
630 cc_bool lShiftDown, rShiftDown;
631 int key;
632
633 if (wParam == VK_SHIFT) {
634 /* The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit
635 to distinguish between left and right keys. Moreover, pressing both keys and releasing one
636 may result in both keys being held down (but not always).*/
637 lShiftDown = ((USHORT)GetKeyState(VK_LSHIFT)) >> 15;
638 rShiftDown = ((USHORT)GetKeyState(VK_RSHIFT)) >> 15;
639
640 if (!pressed || lShiftDown != rShiftDown) {
641 Input_Set(KEY_LSHIFT, lShiftDown);
642 Input_Set(KEY_RSHIFT, rShiftDown);
643 }
644 } else {
645 key = MapNativeKey(wParam, lParam);
646 if (key) Input_Set(key, pressed);
647 else Platform_Log1("Unknown key: %x", &wParam);
648 }
649 return 0;
650 } break;
651
652 case WM_SYSCHAR:
653 return 0;
654
655 case WM_KILLFOCUS:
656 /* TODO: Keep track of keyboard when focus is lost */
657 Input_Clear();
658 break;
659
660 case WM_CLOSE:
661 Event_RaiseVoid(&WindowEvents.Closing);
662 if (WindowInfo.Exists) DestroyWindow(win_handle);
663 WindowInfo.Exists = false0;
664 break;
665
666 case WM_DESTROY:
667 WindowInfo.Exists = false0;
668 UnregisterClass(CC_WIN_CLASSNAME, win_instance);
669
670 if (win_DC) ReleaseDC(win_handle, win_DC);
671 break;
672 }
673 return is_ansiWindow ? DefWindowProcA(handle, message, wParam, lParam)
674 : DefWindowProcW(handle, message, wParam, lParam);
675}
676
677
678/*########################################################################################################################*
679*--------------------------------------------------Public implementation--------------------------------------------------*
680*#########################################################################################################################*/
681void Window_Init(void) {
682 HDC hdc = GetDC(NULL((void*)0));
683 DisplayInfo.Width = GetSystemMetrics(SM_CXSCREEN);
684 DisplayInfo.Height = GetSystemMetrics(SM_CYSCREEN);
685 DisplayInfo.Depth = GetDeviceCaps(hdc, BITSPIXEL);
686 DisplayInfo.ScaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;
687 DisplayInfo.ScaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f;
688 ReleaseDC(NULL((void*)0), hdc);
689}
690
691static ATOM DoRegisterClass(void) {
692 ATOM atom;
693 WNDCLASSEXW wc = { 0 };
694 wc.cbSize = sizeof(WNDCLASSEXW);
695 wc.style = CS_OWNDC;
696 wc.hInstance = win_instance;
697 wc.lpfnWndProc = Window_Procedure;
698 wc.lpszClassName = CC_WIN_CLASSNAME;
699
700 wc.hIcon = (HICON)LoadImage(win_instance, MAKEINTRESOURCE(1), IMAGE_ICON,
701 GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0);
702 wc.hIconSm = (HICON)LoadImage(win_instance, MAKEINTRESOURCE(1), IMAGE_ICON,
703 GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
704 wc.hCursor = LoadCursor(NULL((void*)0), IDC_ARROW);
705
706 if ((atom = RegisterClassExW(&wc))) return atom;
707 /* Windows 9x does not support W API functions */
708 return RegisterClassExA((const WNDCLASSEXA*)&wc);
709}
710
711static void DoCreateWindow(ATOM atom, int width, int height) {
712 cc_result res;
713 RECT r;
714 /* Calculate final window rectangle after window decorations are added (titlebar, borders etc) */
715 r.left = Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2); r.right = r.left + width;
716 r.top = Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2); r.bottom = r.top + height;
717 AdjustWindowRect(&r, CC_WIN_STYLE, false0);
718
719 if ((win_handle = CreateWindowExW(0, MAKEINTATOM(atom), NULL((void*)0), CC_WIN_STYLE,
720 r.left, r.top, Rect_Width(r), Rect_Height(r), NULL((void*)0), NULL((void*)0), win_instance, NULL((void*)0)))) return;
721 res = GetLastError();
722
723 /* Windows 9x does not support W API functions */
724 if (res == ERROR_CALL_NOT_IMPLEMENTED) {
725 is_ansiWindow = true1;
726 if ((win_handle = CreateWindowExA(0, MAKEINTATOM(atom), NULL((void*)0), CC_WIN_STYLE,
727 r.left, r.top, Rect_Width(r), Rect_Height(r), NULL((void*)0), NULL((void*)0), win_instance, NULL((void*)0)))) return;
728 res = GetLastError();
729 }
730 Logger_Abort2(res, "Failed to create window");
731}
732
733void Window_Create(int width, int height) {
734 ATOM atom;
735 win_instance = GetModuleHandle(NULL((void*)0));
736 /* TODO: UngroupFromTaskbar(); */
737 width = Display_ScaleX(width);
738 height = Display_ScaleY(height);
739
740 atom = DoRegisterClass();
741 DoCreateWindow(atom, width, height);
742 RefreshWindowBounds();
743
744 win_DC = GetDC(win_handle);
745 if (!win_DC) Logger_Abort2(GetLastError(), "Failed to get device context");
746 WindowInfo.Exists = true1;
747 WindowInfo.Handle = win_handle;
748}
749
750void Window_SetTitle(const cc_string* title) {
751 WCHAR str[NATIVE_STR_LEN600];
752 Platform_EncodeUtf16(str, title);
753 SetWindowTextW(win_handle, str);
754}
755
756void Clipboard_GetText(cc_string* value) {
757 cc_bool unicode;
758 HANDLE hGlobal;
759 LPVOID src;
760 SIZE_T size;
761 int i;
762
763 /* retry up to 50 times */
764 for (i = 0; i < 50; i++) {
765 if (!OpenClipboard(win_handle)) {
766 Thread_Sleep(10);
767 continue;
768 }
769
770 unicode = true1;
771 hGlobal = GetClipboardData(CF_UNICODETEXT);
772 if (!hGlobal) {
773 hGlobal = GetClipboardData(CF_TEXT);
774 unicode = false0;
775 }
776
777 if (!hGlobal) { CloseClipboard(); return; }
778 src = GlobalLock(hGlobal);
779 size = GlobalSize(hGlobal);
780
781 /* ignore trailing NULL at end */
782 /* TODO: Verify it's always there */
783 if (unicode) {
784 String_AppendUtf16(value, src, size - 2);
785 } else {
786 String_DecodeCP1252(value, src, size - 1);
787 }
788
789 GlobalUnlock(hGlobal);
790 CloseClipboard();
791 return;
792 }
793}
794
795void Clipboard_SetText(const cc_string* value) {
796 cc_unichar* text;
797 HANDLE hGlobal;
798 int i;
799
800 /* retry up to 10 times */
801 for (i = 0; i < 10; i++) {
802 if (!OpenClipboard(win_handle)) {
803 Thread_Sleep(100);
804 continue;
805 }
806
807 hGlobal = GlobalAlloc(GMEM_MOVEABLE, (value->length + 1) * 2);
808 if (!hGlobal) { CloseClipboard(); return; }
809
810 text = (cc_unichar*)GlobalLock(hGlobal);
811 for (i = 0; i < value->length; i++, text++) {
812 *text = Convert_CP437ToUnicode(value->buffer[i]);
813 }
814 *text = '\0';
815
816 GlobalUnlock(hGlobal);
817 EmptyClipboard();
818 SetClipboardData(CF_UNICODETEXT, hGlobal);
819 CloseClipboard();
820 return;
821 }
822}
823
824void Window_Show(void) {
825 ShowWindow(win_handle, SW_SHOW);
826 BringWindowToTop(win_handle);
827 SetForegroundWindow(win_handle);
828}
829
830int Window_GetWindowState(void) {
831 DWORD s = GetWindowLong(win_handle, GWL_STYLE);
832
833 if ((s & WS_MINIMIZE)) return WINDOW_STATE_MINIMISED;
834 if ((s & WS_MAXIMIZE) && (s & WS_POPUP)) return WINDOW_STATE_FULLSCREEN;
835 return WINDOW_STATE_NORMAL;
836}
837
838static void ToggleFullscreen(cc_bool fullscreen, UINT finalShow) {
839 DWORD style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
840 style |= (fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
841
842 suppress_resize = true1;
843 {
844 ShowWindow(win_handle, SW_RESTORE); /* reset maximised state */
845 SetWindowLong(win_handle, GWL_STYLE, style);
846 SetWindowPos(win_handle, NULL((void*)0), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
847 ShowWindow(win_handle, finalShow);
848 Window_ProcessEvents();
849 }
850 suppress_resize = false0;
851
852 /* call Resized event only once */
853 RefreshWindowBounds();
854 Event_RaiseVoid(&WindowEvents.Resized);
855}
856
857static UINT win_show;
858cc_result Window_EnterFullscreen(void) {
859 WINDOWPLACEMENT w = { 0 };
860 w.length = sizeof(WINDOWPLACEMENT);
861 GetWindowPlacement(win_handle, &w);
862
863 win_show = w.showCmd;
864 ToggleFullscreen(true1, SW_MAXIMIZE);
865 return 0;
866}
867
868cc_result Window_ExitFullscreen(void) {
869 ToggleFullscreen(false0, win_show);
870 return 0;
871}
872
873
874void Window_SetSize(int width, int height) {
875 DWORD style = GetWindowLong(win_handle, GWL_STYLE);
876 RECT rect = { 0, 0, width, height };
877 AdjustWindowRect(&rect, style, false0);
878
879 SetWindowPos(win_handle, NULL((void*)0), 0, 0,
880 Rect_Width(rect), Rect_Height(rect), SWP_NOMOVE);
881}
882
883void Window_Close(void) {
884 PostMessage(win_handle, WM_CLOSE, 0, 0);
885}
886
887void Window_ProcessEvents(void) {
888 HWND foreground;
889 MSG msg;
890 while (PeekMessage(&msg, NULL((void*)0), 0, 0, 1)) {
891 TranslateMessage(&msg);
892 DispatchMessage(&msg);
893 }
894
895 foreground = GetForegroundWindow();
896 if (foreground) {
897 WindowInfo.Focused = foreground == win_handle;
898 }
899}
900
901static void Cursor_GetRawPos(int* x, int* y) {
902 POINT point;
903 GetCursorPos(&point);
904 *x = point.x; *y = point.y;
905}
906
907void Cursor_SetPosition(int x, int y) {
908 SetCursorPos(x + windowX, y + windowY);
909}
910static void Cursor_DoSetVisible(cc_bool visible) {
911 int i;
912 /* ShowCursor actually is a counter (returns > 0 if visible, <= 0 if not) */
913 /* Try multiple times in case cursor count was changed by something else */
914 if (visible) {
915 for (i = 0; i < 10 && ShowCursor(true1) < 0; i++) { }
916 } else {
917 for (i = 0; i < 10 && ShowCursor(false0) >= 0; i++) {}
918 }
919}
920
921static void ShowDialogCore(const char* title, const char* msg) {
922 MessageBoxA(win_handle, msg, title, 0);
923}
924
925static HDC draw_DC;
926static HBITMAP draw_DIB;
927void Window_AllocFramebuffer(struct Bitmap* bmp) {
928 BITMAPINFO hdr = { 0 };
929 if (!draw_DC) draw_DC = CreateCompatibleDC(win_DC);
930
931 /* sizeof(BITMAPINFO) does not work on Windows 9x */
932 hdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
933 hdr.bmiHeader.biWidth = bmp->width;
934 hdr.bmiHeader.biHeight = -bmp->height;
935 hdr.bmiHeader.biBitCount = 32;
936 hdr.bmiHeader.biPlanes = 1;
937
938 draw_DIB = CreateDIBSection(draw_DC, &hdr, DIB_RGB_COLORS, (void**)&bmp->scan0, NULL((void*)0), 0);
939 if (!draw_DIB) Logger_Abort2(GetLastError(), "Failed to create DIB");
940}
941
942void Window_DrawFramebuffer(Rect2D r) {
943 HGDIOBJ oldSrc = SelectObject(draw_DC, draw_DIB);
944 BitBlt(win_DC, r.X, r.Y, r.Width, r.Height, draw_DC, r.X, r.Y, SRCCOPY);
945 SelectObject(draw_DC, oldSrc);
946}
947
948void Window_FreeFramebuffer(struct Bitmap* bmp) {
949 DeleteObject(draw_DIB);
950}
951
952static cc_bool rawMouseInited, rawMouseSupported;
953static void InitRawMouse(void) {
954 static const cc_string user32 = String_FromConst("USER32.DLL"){ "USER32.DLL", (sizeof("USER32.DLL") - 1), (sizeof("USER32.DLL"
) - 1)}
;
955 void* lib;
956 RAWINPUTDEVICE rid;
957
958 if ((lib = DynamicLib_Load2(&user32))) {
959 _registerRawInput = (FUNC_RegisterRawInput)DynamicLib_Get2(lib, "RegisterRawInputDevices");
960 _getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
961 rawMouseSupported = _registerRawInput && _getRawInputData;
962 }
963 if (!rawMouseSupported) { Platform_LogConst("Raw input unsupported!"); return; }
964
965 rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */
966 rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */
967 rid.dwFlags = RIDEV_INPUTSINK;
968 rid.hwndTarget = win_handle;
969
970 if (_registerRawInput(&rid, 1, sizeof(rid))) return;
971 Logger_SysWarn(GetLastError(), "initing raw mouse");
972 rawMouseSupported = false0;
973}
974
975void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
976void Window_SetKeyboardText(const cc_string* text) { }
977void Window_CloseKeyboard(void) { }
978
979void Window_EnableRawMouse(void) {
980 DefaultEnableRawMouse();
981 if (!rawMouseInited) InitRawMouse();
982 rawMouseInited = true1;
983}
984
985void Window_UpdateRawMouse(void) {
986 if (rawMouseSupported) {
987 /* handled in WM_INPUT messages */
988 CentreMousePosition();
989 } else {
990 DefaultUpdateRawMouse();
991 }
992}
993
994void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
995
996
997/*########################################################################################################################*
998*-------------------------------------------------------X11 window--------------------------------------------------------*
999*#########################################################################################################################*/
1000#elif defined CC_BUILD_X11
1001#include <X11/Xlib.h>
1002#include <X11/Xutil.h>
1003#include <X11/XKBlib.h>
1004#include <X11/extensions/XInput2.h>
1005
1006#ifdef X_HAVE_UTF8_STRING1
1007#define CC_BUILD_XIM
1008/* XIM support based off details described in */
1009/* https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/ */
1010#endif
1011
1012#define _NET_WM_STATE_REMOVE0 0
1013#define _NET_WM_STATE_ADD1 1
1014#define _NET_WM_STATE_TOGGLE2 2
1015
1016static Display* win_display;
1017static Window win_rootWin, win_handle;
1018static XVisualInfo win_visual;
1019#ifdef CC_BUILD_XIM
1020static XIM win_xim;
1021static XIC win_xic;
1022#endif
1023
1024static Atom wm_destroy, net_wm_state, net_wm_ping;
1025static Atom net_wm_state_minimized;
1026static Atom net_wm_state_fullscreen;
1027
1028static Atom xa_clipboard, xa_targets, xa_utf8_string, xa_data_sel;
1029static Atom xa_atom = 4;
1030static long win_eventMask;
1031static cc_bool grabCursor;
1032
1033static int MapNativeKey(KeySym key, unsigned int state) {
1034 if (key >= XK_00x0030 && key <= XK_90x0039) { return '0' + (key - XK_00x0030); }
1035 if (key >= XK_A0x0041 && key <= XK_Z0x005a) { return 'A' + (key - XK_A0x0041); }
1036 if (key >= XK_a0x0061 && key <= XK_z0x007a) { return 'A' + (key - XK_a0x0061); }
1037
1038 if (key >= XK_F10xffbe && key <= XK_F240xffd5) { return KEY_F1 + (key - XK_F10xffbe); }
1039 if (key >= XK_KP_00xffb0 && key <= XK_KP_90xffb9) { return KEY_KP0 + (key - XK_KP_00xffb0); }
1040
1041 /* Same Num Lock behaviour as Windows and text editors */
1042 if (key >= XK_KP_Home0xff95 && key <= XK_KP_Delete0xff9f && !(state & Mod2Mask(1<<4))) {
1043 if (key == XK_KP_Home0xff95) return KEY_HOME;
1044 if (key == XK_KP_Up0xff97) return KEY_UP;
1045 if (key == XK_KP_Page_Up0xff9a) return KEY_PAGEUP;
1046
1047 if (key == XK_KP_Left0xff96) return KEY_LEFT;
1048 if (key == XK_KP_Insert0xff9e) return KEY_INSERT;
1049 if (key == XK_KP_Right0xff98) return KEY_RIGHT;
1050
1051 if (key == XK_KP_End0xff9c) return KEY_END;
1052 if (key == XK_KP_Down0xff99) return KEY_DOWN;
1053 if (key == XK_KP_Page_Down0xff9b) return KEY_PAGEDOWN;
1054 }
1055
1056 /* A chromebook user reported issues with pressing some keys: */
1057 /* tilde - "Unknown key press: (8000060, 800007E) */
1058 /* quote - "Unknown key press: (8000027, 8000022) */
1059 /* Note if 8000 is stripped, you get '0060' (XK_grave) and 0027 (XK_apostrophe) */
1060 /* ChromeOS seems to also mask to 0xFFFF, so I also do so here */
1061 /* https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_x.cc */
1062 key &= 0xFFFF;
1063
1064 switch (key) {
1065 case XK_Escape0xff1b: return KEY_ESCAPE;
1066 case XK_Return0xff0d: return KEY_ENTER;
1067 case XK_space0x0020: return KEY_SPACE;
1068 case XK_BackSpace0xff08: return KEY_BACKSPACE;
1069
1070 case XK_Shift_L0xffe1: return KEY_LSHIFT;
1071 case XK_Shift_R0xffe2: return KEY_RSHIFT;
1072 case XK_Alt_L0xffe9: return KEY_LALT;
1073 case XK_Alt_R0xffea: return KEY_RALT;
1074 case XK_Control_L0xffe3: return KEY_LCTRL;
1075 case XK_Control_R0xffe4: return KEY_RCTRL;
1076 case XK_Super_L0xffeb: return KEY_LWIN;
1077 case XK_Super_R0xffec: return KEY_RWIN;
1078 case XK_Meta_L0xffe7: return KEY_LWIN;
1079 case XK_Meta_R0xffe8: return KEY_RWIN;
1080
1081 case XK_Menu0xff67: return KEY_MENU;
1082 case XK_Tab0xff09: return KEY_TAB;
1083 case XK_minus0x002d: return KEY_MINUS;
1084 case XK_plus0x002b: return KEY_EQUALS;
1085 case XK_equal0x003d: return KEY_EQUALS;
1086
1087 case XK_Caps_Lock0xffe5: return KEY_CAPSLOCK;
1088 case XK_Num_Lock0xff7f: return KEY_NUMLOCK;
1089
1090 case XK_Pause0xff13: return KEY_PAUSE;
1091 case XK_Break0xff6b: return KEY_PAUSE;
1092 case XK_Scroll_Lock0xff14: return KEY_SCROLLLOCK;
1093 case XK_Insert0xff63: return KEY_INSERT;
1094 case XK_Print0xff61: return KEY_PRINTSCREEN;
1095 case XK_Sys_Req0xff15: return KEY_PRINTSCREEN;
1096
1097 case XK_backslash0x005c: return KEY_BACKSLASH;
1098 case XK_bar0x007c: return KEY_BACKSLASH;
1099 case XK_braceleft0x007b: return KEY_LBRACKET;
1100 case XK_bracketleft0x005b: return KEY_LBRACKET;
1101 case XK_braceright0x007d: return KEY_RBRACKET;
1102 case XK_bracketright0x005d: return KEY_RBRACKET;
1103 case XK_colon0x003a: return KEY_SEMICOLON;
1104 case XK_semicolon0x003b: return KEY_SEMICOLON;
1105 case XK_quoteright0x0027: return KEY_QUOTE;
1106 case XK_quotedbl0x0022: return KEY_QUOTE;
1107 case XK_quoteleft0x0060: return KEY_TILDE;
1108 case XK_asciitilde0x007e: return KEY_TILDE;
1109
1110 case XK_comma0x002c: return KEY_COMMA;
1111 case XK_less0x003c: return KEY_COMMA;
1112 case XK_period0x002e: return KEY_PERIOD;
1113 case XK_greater0x003e: return KEY_PERIOD;
1114 case XK_slash0x002f: return KEY_SLASH;
1115 case XK_question0x003f: return KEY_SLASH;
1116
1117 case XK_Left0xff51: return KEY_LEFT;
1118 case XK_Down0xff54: return KEY_DOWN;
1119 case XK_Right0xff53: return KEY_RIGHT;
1120 case XK_Up0xff52: return KEY_UP;
1121
1122 case XK_Delete0xffff: return KEY_DELETE;
1123 case XK_Home0xff50: return KEY_HOME;
1124 case XK_End0xff57: return KEY_END;
1125 case XK_Page_Up0xff55: return KEY_PAGEUP;
1126 case XK_Page_Down0xff56: return KEY_PAGEDOWN;
1127
1128 case XK_KP_Add0xffab: return KEY_KP_PLUS;
1129 case XK_KP_Subtract0xffad: return KEY_KP_MINUS;
1130 case XK_KP_Multiply0xffaa: return KEY_KP_MULTIPLY;
1131 case XK_KP_Divide0xffaf: return KEY_KP_DIVIDE;
1132 case XK_KP_Decimal0xffae: return KEY_KP_DECIMAL;
1133 case XK_KP_Insert0xff9e: return KEY_KP0;
1134 case XK_KP_End0xff9c: return KEY_KP1;
1135 case XK_KP_Down0xff99: return KEY_KP2;
1136 case XK_KP_Page_Down0xff9b: return KEY_KP3;
1137 case XK_KP_Left0xff96: return KEY_KP4;
1138 case XK_KP_Begin0xff9d: return KEY_KP5;
1139 case XK_KP_Right0xff98: return KEY_KP6;
1140 case XK_KP_Home0xff95: return KEY_KP7;
1141 case XK_KP_Up0xff97: return KEY_KP8;
1142 case XK_KP_Page_Up0xff9a: return KEY_KP9;
1143 case XK_KP_Delete0xff9f: return KEY_KP_DECIMAL;
1144 case XK_KP_Enter0xff8d: return KEY_KP_ENTER;
1145 }
1146 return KEY_NONE;
1147}
1148
1149/* NOTE: This may not be entirely accurate, because user can configure keycode mappings */
1150static const cc_uint8 keycodeMap[136] = {
1151 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, '1', '2', '3', '4', '5', '6',
1152 '7', '8', '9', '0', KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
1153 'O', 'P', KEY_LBRACKET, KEY_RBRACKET, KEY_ENTER, KEY_LCTRL, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', KEY_SEMICOLON,
1154 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,
1155 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,
1156 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,
1157 KEY_F12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1158 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,
1159 0, 0, 0, KEY_PAUSE, 0, 0, 0, 0, 0, KEY_LWIN, 0, KEY_RWIN
1160};
1161
1162static int MapNativeKeycode(unsigned int keycode) {
1163 return keycode < Array_Elems(keycodeMap)(sizeof(keycodeMap) / sizeof(keycodeMap[0])) ? keycodeMap[keycode] : 0;
1164}
1165
1166static void RegisterAtoms(void) {
1167 Display* display = win_display;
1168 wm_destroy = XInternAtom(display, "WM_DELETE_WINDOW", true1);
1169 net_wm_state = XInternAtom(display, "_NET_WM_STATE", false0);
1170 net_wm_ping = XInternAtom(display, "_NET_WM_PING", false0);
1171 net_wm_state_minimized = XInternAtom(display, "_NET_WM_STATE_MINIMIZED", false0);
1172 net_wm_state_fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", false0);
1173
1174 xa_clipboard = XInternAtom(display, "CLIPBOARD", false0);
1175 xa_targets = XInternAtom(display, "TARGETS", false0);
1176 xa_utf8_string = XInternAtom(display, "UTF8_STRING", false0);
1177 xa_data_sel = XInternAtom(display, "CC_SEL_DATA", false0);
1178}
1179
1180static void RefreshWindowBounds(int width, int height) {
1181 if (width != WindowInfo.Width || height != WindowInfo.Height) {
1182 WindowInfo.Width = width;
1183 WindowInfo.Height = height;
1184 Event_RaiseVoid(&WindowEvents.Resized);
1185 }
1186}
1187
1188typedef int (*X11_ErrorHandler)(Display* dpy, XErrorEvent* ev);
1189typedef int (*X11_IOErrorHandler)(Display* dpy);
1190static X11_ErrorHandler realXErrorHandler;
1191static X11_IOErrorHandler realXIOErrorHandler;
1192
1193static void LogXErrorCore(const char* msg) {
1194 char traceBuffer[2048];
1195 cc_string trace;
1196 Platform_LogConst(msg);
1197
1198 String_InitArray(trace, traceBuffer)trace.buffer = traceBuffer; trace.length = 0; trace.capacity =
sizeof(traceBuffer);
;
1199 Logger_Backtrace(&trace, NULL((void*)0));
1200 Platform_Log(traceBuffer, trace.length);
1201}
1202
1203static int LogXError(Display* dpy, XErrorEvent* ev) {
1204 LogXErrorCore("== unhandled X11 error ==");
1205 return realXErrorHandler(dpy, ev);
1206}
1207
1208static int LogXIOError(Display* dpy) {
1209 LogXErrorCore("== unhandled XIO error ==");
1210 return realXIOErrorHandler(dpy);
1211}
1212
1213static void HookXErrors(void) {
1214 realXErrorHandler = XSetErrorHandler(LogXError);
1215 realXIOErrorHandler = XSetIOErrorHandler(LogXIOError);
1216}
1217
1218
1219/*########################################################################################################################*
1220*--------------------------------------------------Public implementation--------------------------------------------------*
1221*#########################################################################################################################*/
1222static XVisualInfo GLContext_SelectVisual(struct GraphicsMode* mode);
1223void Window_Init(void) {
1224 Display* display = XOpenDisplay(NULL((void*)0));
1225 int screen;
1226
1227 if (!display) Logger_Abort("Failed to open display");
1228 screen = DefaultScreen(display)(((_XPrivDisplay)(display))->default_screen);
1229 HookXErrors();
1230
1231 win_display = display;
1232 win_rootWin = RootWindow(display, screen)((&((_XPrivDisplay)(display))->screens[screen])->root
)
;
1233
1234 /* TODO: Use Xinerama and XRandR for querying these */
1235 DisplayInfo.Width = DisplayWidth(display, screen)((&((_XPrivDisplay)(display))->screens[screen])->width
)
;
1236 DisplayInfo.Height = DisplayHeight(display, screen)((&((_XPrivDisplay)(display))->screens[screen])->height
)
;
1237 DisplayInfo.Depth = DefaultDepth(display, screen)((&((_XPrivDisplay)(display))->screens[screen])->root_depth
)
;
1238 DisplayInfo.ScaleX = 1;
1239 DisplayInfo.ScaleY = 1;
1240}
1241
1242#ifdef CC_BUILD_ICON
1243extern const long CCIcon_Data[];
1244extern const int CCIcon_Size;
1245
1246static void ApplyIcon(void) {
1247 Atom net_wm_icon = XInternAtom(win_display, "_NET_WM_ICON", false0);
1248 Atom xa_cardinal = XInternAtom(win_display, "CARDINAL", false0);
1249 XChangeProperty(win_display, win_handle, net_wm_icon, xa_cardinal, 32, PropModeReplace0, CCIcon_Data, CCIcon_Size);
1250}
1251#else
1252static void ApplyIcon(void) { }
1253#endif
1254
1255void Window_Create(int width, int height) {
1256 XSetWindowAttributes attributes = { 0 };
1257 XSizeHints hints = { 0 };
1258 Atom protocols[2];
1259 struct GraphicsMode mode;
1260 int supported, x, y;
1261
1262 x = Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2);
1263 y = Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2);
1264 InitGraphicsMode(&mode);
1265 RegisterAtoms();
1266
1267 win_eventMask = StructureNotifyMask(1L<<17) /*| SubstructureNotifyMask*/ | ExposureMask(1L<<15) |
1268 KeyReleaseMask(1L<<1) | KeyPressMask(1L<<0) | KeymapStateMask(1L<<14) | PointerMotionMask(1L<<6) |
1269 FocusChangeMask(1L<<21) | ButtonPressMask(1L<<2) | ButtonReleaseMask(1L<<3) | EnterWindowMask(1L<<4) |
1270 LeaveWindowMask(1L<<5) | PropertyChangeMask(1L<<22);
1271 win_visual = GLContext_SelectVisual(&mode);
1272
1273 Platform_LogConst("Opening render window... ");
1274 attributes.colormap = XCreateColormap(win_display, win_rootWin, win_visual.visual, AllocNone0);
1275 attributes.event_mask = win_eventMask;
1276
1277 win_handle = XCreateWindow(win_display, win_rootWin, x, y, width, height,
1278 0, win_visual.depth /* CopyFromParent*/, InputOutput1, win_visual.visual,
1279 CWColormap(1L<<13) | CWEventMask(1L<<11) | CWBackPixel(1L<<1) | CWBorderPixel(1L<<3), &attributes);
1280 if (!win_handle) Logger_Abort("XCreateWindow failed");
1281
1282#ifdef CC_BUILD_XIM
1283 win_xim = XOpenIM(win_display, NULL((void*)0), NULL((void*)0), NULL((void*)0));
1284 win_xic = XCreateIC(win_xim, XNInputStyle"inputStyle", XIMPreeditNothing0x0008L | XIMStatusNothing0x0400L,
1285 XNClientWindow"clientWindow", win_handle, NULL((void*)0));
1286#endif
1287
1288 /* Set hints to try to force WM to create window at requested x,y */
1289 /* Without this, some WMs will instead place the window whereever */
1290 hints.base_width = width;
1291 hints.base_height = height;
1292 hints.flags = PSize(1L << 3) | PPosition(1L << 2);
1293 XSetWMNormalHints(win_display, win_handle, &hints);
1294
1295 /* Register for window destroy notification */
1296 protocols[0] = wm_destroy;
1297 protocols[1] = net_wm_ping;
1298 XSetWMProtocols(win_display, win_handle, protocols, 2);
1299
1300 /* Request that auto-repeat is only set on devices that support it physically.
1301 This typically means that it's turned off for keyboards (which is what we want).
1302 We prefer this method over XAutoRepeatOff/On, because the latter needs to
1303 be reset before the program exits. */
1304 XkbSetDetectableAutoRepeat(win_display, true1, &supported);
1305
1306 RefreshWindowBounds(width, height);
1307 WindowInfo.Exists = true1;
1308 WindowInfo.Handle = (void*)win_handle;
1309 grabCursor = Options_GetBool(OPT_GRAB_CURSOR"win-grab-cursor", false0);
1310
1311 /* So right name appears in e.g. Ubuntu Unity launchbar */
1312 XClassHint hint = { 0 };
1313 hint.res_name = GAME_APP_TITLE"ClassiCube 1.2.3";
1314 hint.res_class = GAME_APP_TITLE"ClassiCube 1.2.3";
1315 XSetClassHint(win_display, win_handle, &hint);
1316 ApplyIcon();
1317}
1318
1319void Window_SetTitle(const cc_string* title) {
1320 char str[NATIVE_STR_LEN600];
1321 Platform_EncodeUtf8(str, title);
1322 XStoreName(win_display, win_handle, str);
1323}
1324
1325static char clipboard_copy_buffer[256];
1326static char clipboard_paste_buffer[256];
1327static cc_string clipboard_copy_text = String_FromArray(clipboard_copy_buffer){ clipboard_copy_buffer, 0, sizeof(clipboard_copy_buffer)};
1328static cc_string clipboard_paste_text = String_FromArray(clipboard_paste_buffer){ clipboard_paste_buffer, 0, sizeof(clipboard_paste_buffer)};
1329
1330void Clipboard_GetText(cc_string* value) {
1331 Window owner = XGetSelectionOwner(win_display, xa_clipboard);
1332 int i;
1333 if (!owner) return; /* no window owner */
1334
1335 XConvertSelection(win_display, xa_clipboard, xa_utf8_string, xa_data_sel, win_handle, 0);
1336 clipboard_paste_text.length = 0;
1337
1338 /* wait up to 1 second for SelectionNotify event to arrive */
1339 for (i = 0; i < 100; i++) {
1340 Window_ProcessEvents();
1341 if (clipboard_paste_text.length) {
1342 String_AppendString(value, &clipboard_paste_text);
1343 return;
1344 } else {
1345 Thread_Sleep(10);
1346 }
1347 }
1348}
1349
1350void Clipboard_SetText(const cc_string* value) {
1351 String_Copy(&clipboard_copy_text, value);
1352 XSetSelectionOwner(win_display, xa_clipboard, win_handle, 0);
1353}
1354
1355void Window_Show(void) { XMapWindow(win_display, win_handle); }
1356
1357int Window_GetWindowState(void) {
1358 cc_bool fullscreen = false0, minimised = false0;
1359 Atom prop_type;
1360 unsigned long items, after;
1361 int i, prop_format;
1362 Atom* data = NULL((void*)0);
1363
1364 XGetWindowProperty(win_display, win_handle,
1365 net_wm_state, 0, 256, false0, xa_atom, &prop_type,
1366 &prop_format, &items, &after, &data);
1367
1368 if (data) {
1369 for (i = 0; i < items; i++) {
1370 Atom atom = data[i];
1371
1372 if (atom == net_wm_state_minimized) {
1373 minimised = true1;
1374 } else if (atom == net_wm_state_fullscreen) {
1375 fullscreen = true1;
1376 }
1377 }
1378 XFree(data);
1379 }
1380
1381 if (fullscreen) return WINDOW_STATE_FULLSCREEN;
1382 if (minimised) return WINDOW_STATE_MINIMISED;
1383 return WINDOW_STATE_NORMAL;
1384}
1385
1386static void ToggleFullscreen(long op) {
1387 XEvent ev = { 0 };
1388 ev.xclient.type = ClientMessage33;
1389 ev.xclient.window = win_handle;
1390 ev.xclient.message_type = net_wm_state;
1391 ev.xclient.format = 32;
1392 ev.xclient.data.l[0] = op;
1393 ev.xclient.data.l[1] = net_wm_state_fullscreen;
1394
1395 XSendEvent(win_display, win_rootWin, false0,
1396 SubstructureRedirectMask(1L<<20) | SubstructureNotifyMask(1L<<19), &ev);
1397 XSync(win_display, false0);
1398 XRaiseWindow(win_display, win_handle);
1399 Window_ProcessEvents();
1400}
1401
1402cc_result Window_EnterFullscreen(void) {
1403 ToggleFullscreen(_NET_WM_STATE_ADD1); return 0;
1404}
1405cc_result Window_ExitFullscreen(void) {
1406 ToggleFullscreen(_NET_WM_STATE_REMOVE0); return 0;
1407}
1408
1409void Window_SetSize(int width, int height) {
1410 XResizeWindow(win_display, win_handle, width, height);
1411 Window_ProcessEvents();
1412}
1413
1414void Window_Close(void) {
1415 XEvent ev = { 0 };
1416 ev.type = ClientMessage33;
1417 ev.xclient.format = 32;
1418 ev.xclient.display = win_display;
1419 ev.xclient.window = win_handle;
1420 ev.xclient.data.l[0] = wm_destroy;
1421
1422 XSendEvent(win_display, win_handle, false0, 0, &ev);
1423 XFlush(win_display);
1424}
1425
1426static int MapNativeMouse(int button) {
1427 if (button == 1) return KEY_LMOUSE;
1428 if (button == 2) return KEY_MMOUSE;
1429 if (button == 3) return KEY_RMOUSE;
1430 if (button == 8) return KEY_XBUTTON1;
1431 if (button == 9) return KEY_XBUTTON2;
1432 return 0;
1433}
1434
1435static int TryGetKey(XKeyEvent* ev) {
1436 KeySym keysym1 = XLookupKeysym(ev, 0);
1437 KeySym keysym2 = XLookupKeysym(ev, 1);
1438
1439 int key = MapNativeKey(keysym1, ev->state);
1440 if (!key) key = MapNativeKey(keysym2, ev->state);
1441 if (key) return key;
1442
1443 Platform_Log3("Unknown key %i (%x, %x)", &ev->keycode, &keysym1, &keysym2);
1444 /* The user may be using a keyboard layout such as cryllic - */
1445 /* fallback to trying to conver the raw scancodes instead */
1446 return MapNativeKeycode(ev->keycode);
1447}
1448
1449static Atom Window_GetSelectionProperty(XEvent* e) {
1450 Atom prop = e->xselectionrequest.property;
1451 if (prop) return prop;
1452
1453 /* For obsolete clients. See ICCCM spec, selections chapter for reasoning. */
1454 return e->xselectionrequest.target;
1455}
1456
1457static Boolint FilterEvent(Display* d, XEvent* e, XPointer w) {
1458 return
1459 e->xany.window == (Window)w ||
1460 !e->xany.window || /* KeymapNotify events don't have a window */
1461 e->type == GenericEvent35; /* For XInput events */
1462}
1463
1464static void HandleWMDestroy(void) {
1465 Platform_LogConst("Exit message received.");
1466 Event_RaiseVoid(&WindowEvents.Closing);
1467
1468 /* sync and discard all events queued */
1469 XSync(win_display, true1);
1470 XDestroyWindow(win_display, win_handle);
1471 WindowInfo.Exists = false0;
1472}
1473
1474static void HandleWMPing(XEvent* e) {
1475 e->xany.window = win_rootWin;
1476 XSendEvent(win_display, win_rootWin, false0,
1477 SubstructureRedirectMask(1L<<20) | SubstructureNotifyMask(1L<<19), e);
1478}
1479static void HandleGenericEvent(XEvent* e);
1480
1481void Window_ProcessEvents(void) {
1482 XEvent e;
1483 int i, btn, key, status;
1484
1485 while (WindowInfo.Exists) {
1486 if (!XCheckIfEvent(win_display, &e, FilterEvent, (XPointer)win_handle)) break;
1487 if (XFilterEvent(&e, None0L) == True1) continue;
1488
1489 switch (e.type) {
1490 case GenericEvent35:
1491 HandleGenericEvent(&e); break;
1492 case ClientMessage33:
1493 if (e.xclient.data.l[0] == wm_destroy) {
1494 HandleWMDestroy();
1495 } else if (e.xclient.data.l[0] == net_wm_ping) {
1496 HandleWMPing(&e);
1497 }
1498 break;
1499
1500 case DestroyNotify17:
1501 Platform_LogConst("Window destroyed");
1502 WindowInfo.Exists = false0;
1503 break;
1504
1505 case ConfigureNotify22:
1506 RefreshWindowBounds(e.xconfigure.width, e.xconfigure.height);
1507 break;
1508
1509 case Expose12:
1510 if (e.xexpose.count == 0) Event_RaiseVoid(&WindowEvents.Redraw);
1511 break;
1512
1513 case KeyPress2:
1514 {
1515 char data[64], c;
1516 key = TryGetKey(&e.xkey);
1517 if (key) Input_SetPressed(key);
1518
1519#ifdef CC_BUILD_XIM
1520 cc_codepoint cp;
1521 char* chars = data;
1522
1523 status = Xutf8LookupString(win_xic, &e.xkey, data, Array_Elems(data)(sizeof(data) / sizeof(data[0])), NULL((void*)0), NULL((void*)0));
1524 for (; status > 0; status -= i) {
1525 i = Convert_Utf8ToCodepoint(&cp, chars, status);
1526 if (!i) break;
1527
1528 if (Convert_TryCodepointToCP437(cp, &c)) Event_RaiseInt(&InputEvents.Press, c);
1529 chars += i;
1530 }
1531#else
1532 /* This only really works for latin keys (e.g. so some finnish keys still work) */
1533 status = XLookupString(&e.xkey, data, Array_Elems(data)(sizeof(data) / sizeof(data[0])), NULL((void*)0), NULL((void*)0));
1534 for (i = 0; i < status; i++) {
1535 if (!Convert_TryCodepointToCP437((cc_uint8)data[i], &c)) continue;
1536 Event_RaiseInt(&InputEvents.Press, c);
1537 }
1538#endif
1539 } break;
1540
1541 case KeyRelease3:
1542 key = TryGetKey(&e.xkey);
1543 if (key) Input_SetReleased(key);
1544 break;
1545
1546 case ButtonPress4:
1547 btn = MapNativeMouse(e.xbutton.button);
1548 if (btn) Input_SetPressed(btn);
1549 else if (e.xbutton.button == 4) Mouse_ScrollWheel(+1);
1550 else if (e.xbutton.button == 5) Mouse_ScrollWheel(-1);
1551 break;
1552
1553 case ButtonRelease5:
1554 btn = MapNativeMouse(e.xbutton.button);
1555 if (btn) Input_SetReleased(btn);
1556 break;
1557
1558 case MotionNotify6:
1559 Pointer_SetPosition(0, e.xmotion.x, e.xmotion.y);
1560 break;
1561
1562 case FocusIn9:
1563 case FocusOut10:
1564 /* Don't lose focus when another app grabs key or mouse */
1565 if (e.xfocus.mode == NotifyGrab1 || e.xfocus.mode == NotifyUngrab2) break;
1566
1567 WindowInfo.Focused = e.type == FocusIn9;
1568 Event_RaiseVoid(&WindowEvents.FocusChanged);
1569 /* TODO: Keep track of keyboard when focus is lost */
1570 if (!WindowInfo.Focused) Input_Clear();
1571 break;
1572
1573 case MappingNotify34:
1574 if (e.xmapping.request == MappingModifier0 || e.xmapping.request == MappingKeyboard1) {
1575 Platform_LogConst("keybard mapping refreshed");
1576 XRefreshKeyboardMapping(&e.xmapping);
1577 }
1578 break;
1579
1580 case PropertyNotify28:
1581 if (e.xproperty.atom == net_wm_state) {
1582 Event_RaiseVoid(&WindowEvents.StateChanged);
1583 }
1584 break;
1585
1586 case SelectionNotify31:
1587 clipboard_paste_text.length = 0;
1588
1589 if (e.xselection.selection == xa_clipboard && e.xselection.target == xa_utf8_string && e.xselection.property == xa_data_sel) {
1590 Atom prop_type;
1591 int prop_format;
1592 unsigned long items, after;
1593 cc_uint8* data = NULL((void*)0);
1594
1595 XGetWindowProperty(win_display, win_handle, xa_data_sel, 0, 1024, false0, 0,
1596 &prop_type, &prop_format, &items, &after, &data);
1597 XDeleteProperty(win_display, win_handle, xa_data_sel);
1598
1599 if (data && items && prop_type == xa_utf8_string) {
1600 clipboard_paste_text.length = 0;
1601 String_AppendUtf8(&clipboard_paste_text, data, items);
1602 }
1603 if (data) XFree(data);
1604 }
1605 break;
1606
1607 case SelectionRequest30:
1608 {
1609 XEvent reply = { 0 };
1610 reply.xselection.type = SelectionNotify31;
1611 reply.xselection.send_event = true1;
1612 reply.xselection.display = win_display;
1613 reply.xselection.requestor = e.xselectionrequest.requestor;
1614 reply.xselection.selection = e.xselectionrequest.selection;
1615 reply.xselection.target = e.xselectionrequest.target;
1616 reply.xselection.property = 0;
1617 reply.xselection.time = e.xselectionrequest.time;
1618
1619 if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_utf8_string && clipboard_copy_text.length) {
1620 reply.xselection.property = Window_GetSelectionProperty(&e);
1621 char str[800];
1622 int len = Platform_EncodeUtf8(str, &clipboard_copy_text);
1623
1624 XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_utf8_string, 8,
1625 PropModeReplace0, (unsigned char*)str, len);
1626 } else if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_targets) {
1627 reply.xselection.property = Window_GetSelectionProperty(&e);
1628
1629 Atom data[2] = { xa_utf8_string, xa_targets };
1630 XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_atom, 32,
1631 PropModeReplace0, (unsigned char*)data, 2);
1632 }
1633 XSendEvent(win_display, e.xselectionrequest.requestor, true1, 0, &reply);
1634 } break;
1635 }
1636 }
1637}
1638
1639static void Cursor_GetRawPos(int* x, int* y) {
1640 Window rootW, childW;
1641 int childX, childY;
1642 unsigned int mask;
1643 XQueryPointer(win_display, win_rootWin, &rootW, &childW, x, y, &childX, &childY, &mask);
1644}
1645
1646void Cursor_SetPosition(int x, int y) {
1647 XWarpPointer(win_display, None0L, win_handle, 0, 0, 0, 0, x, y);
1648 XFlush(win_display); /* TODO: not sure if XFlush call is necessary */
1649}
1650
1651static Cursor blankCursor;
1652static void Cursor_DoSetVisible(cc_bool visible) {
1653 if (visible) {
1654 XUndefineCursor(win_display, win_handle);
1655 } else {
1656 if (!blankCursor) {
1657 char data = 0;
1658 XColor col = { 0 };
1659 Pixmap pixmap = XCreateBitmapFromData(win_display, win_handle, &data, 1, 1);
1660 blankCursor = XCreatePixmapCursor(win_display, pixmap, pixmap, &col, &col, 0, 0);
1661 XFreePixmap(win_display, pixmap);
1662 }
1663 XDefineCursor(win_display, win_handle, blankCursor);
1664 }
1665}
1666
1667
1668/*########################################################################################################################*
1669*-----------------------------------------------------X11 message box-----------------------------------------------------*
1670*#########################################################################################################################*/
1671struct X11MessageBox {
1672 Window win;
1673 Display* dpy;
1674 GC gc;
1675 unsigned long white, black, background;
1676 unsigned long btnBorder, highlight, shadow;
1677};
1678
1679static unsigned long X11_Col(struct X11MessageBox* m, cc_uint8 r, cc_uint8 g, cc_uint8 b) {
1680 Colormap cmap = XDefaultColormap(m->dpy, DefaultScreen(m->dpy)(((_XPrivDisplay)(m->dpy))->default_screen));
1681 XColor col = { 0 };
1682 col.red = r << 8;
1683 col.green = g << 8;
1684 col.blue = b << 8;
1685 col.flags = DoRed(1<<0) | DoGreen(1<<1) | DoBlue(1<<2);
1686
1687 XAllocColor(m->dpy, cmap, &col);
1688 return col.pixel;
1689}
1690
1691static void X11MessageBox_Init(struct X11MessageBox* m) {
1692 m->black = BlackPixel(m->dpy, DefaultScreen(m->dpy))((&((_XPrivDisplay)(m->dpy))->screens[(((_XPrivDisplay
)(m->dpy))->default_screen)])->black_pixel)
;
1693 m->white = WhitePixel(m->dpy, DefaultScreen(m->dpy))((&((_XPrivDisplay)(m->dpy))->screens[(((_XPrivDisplay
)(m->dpy))->default_screen)])->white_pixel)
;
1694 m->background = X11_Col(m, 206, 206, 206);
1695
1696 m->btnBorder = X11_Col(m, 60, 60, 60);
1697 m->highlight = X11_Col(m, 144, 144, 144);
1698 m->shadow = X11_Col(m, 49, 49, 49);
1699
1700 m->win = XCreateSimpleWindow(m->dpy, DefaultRootWindow(m->dpy)((&((_XPrivDisplay)(m->dpy))->screens[(((_XPrivDisplay
)(m->dpy))->default_screen)])->root)
, 0, 0, 100, 100,
1701 0, m->black, m->background);
1702 XSelectInput(m->dpy, m->win, ExposureMask(1L<<15) | StructureNotifyMask(1L<<17) |
1703 KeyReleaseMask(1L<<1) | PointerMotionMask(1L<<6) |
1704 ButtonPressMask(1L<<2) | ButtonReleaseMask(1L<<3) );
1705
1706 m->gc = XCreateGC(m->dpy, m->win, 0, NULL((void*)0));
1707 XSetForeground(m->dpy, m->gc, m->black);
1708 XSetBackground(m->dpy, m->gc, m->background);
1709}
1710
1711static void X11MessageBox_Free(struct X11MessageBox* m) {
1712 XFreeGC(m->dpy, m->gc);
1713 XDestroyWindow(m->dpy, m->win);
1714}
1715
1716struct X11Textbox {
1717 int x, y, width, height;
1718 int lineHeight, descent;
1719 const char* text;
1720};
1721
1722static void X11Textbox_Measure(struct X11Textbox* t, XFontStruct* font) {
1723 cc_string str = String_FromReadonly(t->text), line;
1724 XCharStruct overall;
1725 int direction, ascent, descent, lines = 0;
10
'ascent' declared without an initial value
1726
1727 for (; str.length; lines++) {
11
Loop condition is false. Execution continues on line 1733
1728 String_UNSAFE_SplitBy(&str, '\n', &line);
1729 XTextExtents(font, line.buffer, line.length, &direction, &ascent, &descent, &overall);
1730 t->width = max(overall.width, t->width)((overall.width) > (t->width) ? (overall.width) : (t->
width))
;
1731 }
1732
1733 t->lineHeight = ascent + descent;
12
The left operand of '+' is a garbage value
1734 t->descent = descent;
1735 t->height = t->lineHeight * lines;
1736}
1737
1738static void X11Textbox_Draw(struct X11Textbox* t, struct X11MessageBox* m) {
1739 cc_string str = String_FromReadonly(t->text), line;
1740 int y = t->y + t->lineHeight - t->descent; /* TODO: is -descent even right? */
1741
1742 for (; str.length; y += t->lineHeight) {
1743 String_UNSAFE_SplitBy(&str, '\n', &line);
1744 XDrawString(m->dpy, m->win, m->gc, t->x, y, line.buffer, line.length);
1745 }
1746}
1747
1748struct X11Button {
1749 int x, y, width, height;
1750 cc_bool clicked;
1751 struct X11Textbox text;
1752};
1753
1754static void X11Button_Draw(struct X11Button* b, struct X11MessageBox* m) {
1755 struct X11Textbox* t;
1756 int begX, endX, begY, endY;
1757
1758 XSetForeground(m->dpy, m->gc, m->btnBorder);
1759 XDrawRectangle(m->dpy, m->win, m->gc, b->x, b->y,
1760 b->width, b->height);
1761
1762 t = &b->text;
1763 begX = b->x + 1; endX = b->x + b->width - 1;
1764 begY = b->y + 1; endY = b->y + b->height - 1;
1765
1766 if (b->clicked) {
1767 XSetForeground(m->dpy, m->gc, m->highlight);
1768 XDrawRectangle(m->dpy, m->win, m->gc, begX, begY,
1769 endX - begX, endY - begY);
1770 } else {
1771 XSetForeground(m->dpy, m->gc, m->white);
1772 XDrawLine(m->dpy, m->win, m->gc, begX, begY,
1773 endX - 1, begY);
1774 XDrawLine(m->dpy, m->win, m->gc, begX, begY,
1775 begX, endY - 1);
1776
1777 XSetForeground(m->dpy, m->gc, m->highlight);
1778 XDrawLine(m->dpy, m->win, m->gc, begX + 1, endY - 1,
1779 endX - 1, endY - 1);
1780 XDrawLine(m->dpy, m->win, m->gc, endX - 1, begY + 1,
1781 endX - 1, endY - 1);
1782
1783 XSetForeground(m->dpy, m->gc, m->shadow);
1784 XDrawLine(m->dpy, m->win, m->gc, begX, endY, endX, endY);
1785 XDrawLine(m->dpy, m->win, m->gc, endX, begY, endX, endY);
1786 }
1787
1788 XSetForeground(m->dpy, m->gc, m->black);
1789 t->x = b->x + b->clicked + (b->width - t->width) / 2;
1790 t->y = b->y + b->clicked + (b->height - t->height) / 2;
1791 X11Textbox_Draw(t, m);
1792}
1793
1794static int X11Button_Contains(struct X11Button* b, int x, int y) {
1795 return x >= b->x && x < (b->x + b->width) &&
1796 y >= b->y && y < (b->y + b->height);
1797}
1798
1799static Boolint X11_FilterEvent(Display* d, XEvent* e, XPointer w) { return e->xany.window == (Window)w; }
1800static void X11_MessageBox(const char* title, const char* text, struct X11MessageBox* m) {
1801 struct X11Button ok = { 0 };
1802 struct X11Textbox body = { 0 };
1803
1804 Atom protocols[2];
1805 XFontStruct* font;
1806 int x, y, width, height;
1807 XSizeHints hints = { 0 };
1808 int mouseX = -1, mouseY = -1, over;
1809 XEvent e;
1810
1811 X11MessageBox_Init(m);
1812 XMapWindow(m->dpy, m->win);
1813 XStoreName(m->dpy, m->win, title);
1814
1815 protocols[0] = XInternAtom(m->dpy, "WM_DELETE_WINDOW", false0);
1816 protocols[1] = XInternAtom(m->dpy, "_NET_WM_PING", false0);
1817 XSetWMProtocols(m->dpy, m->win, protocols, 2);
1818
1819 font = XQueryFont(m->dpy, XGContextFromGC(m->gc));
1820 if (!font) return;
7
Assuming 'font' is non-null
8
Taking false branch
1821
1822 /* Compute size of widgets */
1823 body.text = text;
1824 X11Textbox_Measure(&body, font);
9
Calling 'X11Textbox_Measure'
1825 ok.text.text = "OK";
1826 X11Textbox_Measure(&ok.text, font);
1827 ok.width = ok.text.width + 70;
1828 ok.height = ok.text.height + 10;
1829
1830 /* Compute size and position of window */
1831 width = body.width + 20;
1832 height = body.height + 20 + ok.height + 20;
1833 x = DisplayWidth (m->dpy, DefaultScreen(m->dpy))((&((_XPrivDisplay)(m->dpy))->screens[(((_XPrivDisplay
)(m->dpy))->default_screen)])->width)
/2 - width/2;
1834 y = DisplayHeight(m->dpy, DefaultScreen(m->dpy))((&((_XPrivDisplay)(m->dpy))->screens[(((_XPrivDisplay
)(m->dpy))->default_screen)])->height)
/2 - height/2;
1835 XMoveResizeWindow(m->dpy, m->win, x, y, width, height);
1836
1837 /* Adjust bounds of widgets */
1838 body.x = 10; body.y = 10;
1839 ok.x = width/2 - ok.width/2;
1840 ok.y = height - ok.height - 10;
1841
1842 /* This marks the window as popup window of the main window */
1843 /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */
1844 /* Depending on WM, removes minimise and doesn't show in taskbar */
1845 if (win_handle) XSetTransientForHint(m->dpy, m->win, win_handle);
1846
1847 XFreeFontInfo(NULL((void*)0), font, 1);
1848 XUnmapWindow(m->dpy, m->win); /* Make window non resizeable */
1849
1850 hints.flags = PSize(1L << 3) | PMinSize(1L << 4) | PMaxSize(1L << 5);
1851 hints.min_width = hints.max_width = hints.base_width = width;
1852 hints.min_height = hints.max_height = hints.base_height = height;
1853
1854 XSetWMNormalHints(m->dpy, m->win, &hints);
1855 XMapRaised(m->dpy, m->win);
1856 XFlush(m->dpy);
1857
1858 for (;;) {
1859 /* The naive solution is to use XNextEvent(m->dpy, &e) here. */
1860 /* However this causes issues as that removes events that */
1861 /* should have been delivered to the main game window. */
1862 /* (e.g. breaks initial window resize with i3 WM) */
1863 XIfEvent(m->dpy, &e, X11_FilterEvent, (XPointer)m->win);
1864
1865 switch (e.type)
1866 {
1867 case ButtonPress4:
1868 case ButtonRelease5:
1869 if (e.xbutton.button != Button11) break;
1870 over = X11Button_Contains(&ok, mouseX, mouseY);
1871
1872 if (ok.clicked && e.type == ButtonRelease5) {
1873 if (over) return;
1874 }
1875 ok.clicked = e.type == ButtonPress4 && over;
1876 /* fallthrough to redraw window */
1877
1878 case Expose12:
1879 case MapNotify19:
1880 XClearWindow(m->dpy, m->win);
1881 X11Textbox_Draw(&body, m);
1882 X11Button_Draw(&ok, m);
1883 XFlush(m->dpy);
1884 break;
1885
1886 case KeyRelease3:
1887 if (XLookupKeysym(&e.xkey, 0) == XK_Escape0xff1b) return;
1888 break;
1889
1890 case ClientMessage33:
1891 /* { WM_DELETE_WINDOW, _NET_WM_PING } */
1892 if (e.xclient.data.l[0] == protocols[0]) return;
1893 if (e.xclient.data.l[0] == protocols[1]) HandleWMPing(&e);
1894 break;
1895
1896 case MotionNotify6:
1897 mouseX = e.xmotion.x; mouseY = e.xmotion.y;
1898 break;
1899 }
1900 }
1901}
1902
1903static void ShowDialogCore(const char* title, const char* msg) {
1904 struct X11MessageBox m = { 0 };
1905 m.dpy = win_display;
1906
1907 /* Failing to create a display means can't display a message box. */
1908 /* However the user might have launched the game through terminal, */
1909 /* so fallback to console instead of just dying from a segfault */
1910 if (!m.dpy) {
4
Assuming field 'dpy' is non-null
5
Taking false branch
1911 Platform_LogConst("### MESSAGE ###");
1912 Platform_LogConst(title);
1913 Platform_LogConst(msg);
1914 return;
1915 }
1916
1917 X11_MessageBox(title, msg, &m);
6
Calling 'X11_MessageBox'
1918 X11MessageBox_Free(&m);
1919 XFlush(m.dpy); /* flush so window disappears immediately */
1920}
1921
1922static GC fb_gc;
1923static XImage* fb_image;
1924void Window_AllocFramebuffer(struct Bitmap* bmp) {
1925 if (!fb_gc) fb_gc = XCreateGC(win_display, win_handle, 0, NULL((void*)0));
1926
1927 bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
1928 fb_image = XCreateImage(win_display, win_visual.visual,
1929 win_visual.depth, ZPixmap2, 0, (char*)bmp->scan0,
1930 bmp->width, bmp->height, 32, 0);
1931}
1932
1933void Window_DrawFramebuffer(Rect2D r) {
1934 XPutImage(win_display, win_handle, fb_gc, fb_image,
1935 r.X, r.Y, r.X, r.Y, r.Width, r.Height);
1936}
1937
1938void Window_FreeFramebuffer(struct Bitmap* bmp) {
1939 XFree(fb_image);
1940 Mem_Free(bmp->scan0);
1941}
1942
1943void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
1944void Window_SetKeyboardText(const cc_string* text) { }
1945void Window_CloseKeyboard(void) { }
1946
1947static cc_bool rawMouseInited, rawMouseSupported;
1948static int xiOpcode;
1949
1950static void CheckMovementDelta(double dx, double dy) {
1951 /* Despite the assumption that XI_RawMotion is relative, */
1952 /* unfortunately there's a few buggy corner cases out there */
1953 /* where absolute coordinates are provided instead. */
1954 /* The ugly code belows tries to detect these corner cases, */
1955 /* and disables XInput2 when that happens */
1956 static int valid, fails;
1957
1958 if (valid) return;
1959 /* The default window resolution is 854 x 480, so if there's */
1960 /* a delta less than half of that, then it's almost certain */
1961 /* that the provided coordinates are relative.*/
1962 if (dx < 300 || dy < 200) { valid = true1; return; }
1963
1964 if (fails++ < 20) return;
1965 /* Checked over 20 times now, but no relative coordinates, */
1966 /* so give up trying to use XInput2 anymore. */
1967 Platform_LogConst("Buggy XInput2 detected, disabling it..");
1968 rawMouseSupported = false0;
1969}
1970
1971static void HandleGenericEvent(XEvent* e) {
1972 const double* values;
1973 XIRawEvent* ev;
1974 double dx, dy;
1975
1976 if (!rawMouseSupported || e->xcookie.extension != xiOpcode) return;
1977 if (!XGetEventData(win_display, &e->xcookie)) return;
1978
1979 if (e->xcookie.evtype == XI_RawMotion17 && Input_RawMode) {
1980 ev = (XIRawEvent*)e->xcookie.data;
1981 values = ev->raw_values;
1982
1983 /* Raw motion events may not always have values for both axes */
1984 dx = XIMaskIsSet(ev->valuators.mask, 0)(((unsigned char*)(ev->valuators.mask))[(0)>>3] &
(1 << ((0) & 7)))
? *values++ : 0;
1985 dy = XIMaskIsSet(ev->valuators.mask, 1)(((unsigned char*)(ev->valuators.mask))[(1)>>3] &
(1 << ((1) & 7)))
? *values++ : 0;
1986
1987 CheckMovementDelta(dx, dy);
1988 /* Using 0.5f here makes the sensitivity about same as normal cursor movement */
1989 Event_RaiseRawMove(&PointerEvents.RawMoved, dx * 0.5f, dy * 0.5f);
1990 }
1991 XFreeEventData(win_display, &e->xcookie);
1992}
1993
1994static void InitRawMouse(void) {
1995 XIEventMask evmask;
1996 unsigned char masks[XIMaskLen(XI_LASTEVENT)(((26) >> 3) + 1)] = { 0 };
1997 int ev, err, major, minor;
1998
1999 if (!XQueryExtension(win_display, "XInputExtension", &xiOpcode, &ev, &err)) {
2000 Platform_LogConst("XInput unsupported");
2001 return;
2002 }
2003
2004 /* Only XInput 2.0 is actually required. However, 2.0 has the annoying */
2005 /* behaviour where raw input is NOT delivered while pointer is grabbed. */
2006 /* (i.e. if you press mouse button, no more raw mouse movement events) */
2007 /* http://wine.1045685.n8.nabble.com/PATCH-0-9-Implement-DInput8-mouse-using-RawInput-and-XInput2-RawEvents-only-td6016923.html */
2008 /* Thankfully XInput >= 2.1 corrects this behaviour */
2009 /* http://who-t.blogspot.com/2011/09/whats-new-in-xi-21-raw-events.html */
2010 major = 2; minor = 2;
2011 if (XIQueryVersion(win_display, &major, &minor) != Success0) {
2012 Platform_Log2("Only XInput %i.%i supported", &major, &minor);
2013 return;
2014 }
2015
2016 /* Sometimes XIQueryVersion will report Success, even though the */
2017 /* supported version is only 2.0! So make sure to handle that. */
2018 if (major < 2 || minor < 2) {
2019 Platform_Log2("Only XInput %i.%i supported", &major, &minor);
2020 return;
2021 }
2022
2023 XISetMask(masks, XI_RawMotion)(((unsigned char*)(masks))[(17)>>3] |= (1 << ((17
) & 7)))
;
2024 evmask.deviceid = XIAllMasterDevices1;
2025 evmask.mask_len = sizeof(masks);
2026 evmask.mask = masks;
2027
2028 XISelectEvents(win_display, win_rootWin, &evmask, 1);
2029 rawMouseSupported = true1;
2030}
2031
2032void Window_EnableRawMouse(void) {
2033 DefaultEnableRawMouse();
2034 if (!rawMouseInited) InitRawMouse();
2035 rawMouseInited = true1;
2036
2037 if (!grabCursor) return;
2038 XGrabPointer(win_display, win_handle, True1, ButtonPressMask(1L<<2) | ButtonReleaseMask(1L<<3) | PointerMotionMask(1L<<6),
2039 GrabModeAsync1, GrabModeAsync1, win_handle, blankCursor, CurrentTime0L);
2040}
2041
2042void Window_UpdateRawMouse(void) {
2043 if (rawMouseSupported) {
2044 /* Handled by XI_RawMotion generic event */
2045 CentreMousePosition();
2046 } else {
2047 DefaultUpdateRawMouse();
2048 }
2049}
2050
2051void Window_DisableRawMouse(void) {
2052 DefaultDisableRawMouse();
2053 if (!grabCursor) return;
2054 XUngrabPointer(win_display, CurrentTime0L);
2055}
2056
2057
2058/*########################################################################################################################*
2059*---------------------------------------------------Carbon/Cocoa window---------------------------------------------------*
2060*#########################################################################################################################*/
2061#elif defined CC_BUILD_CARBON || defined CC_BUILD_COCOA
2062#include <ApplicationServices/ApplicationServices.h>
2063static int windowX, windowY;
2064
2065static void Window_CommonInit(void) {
2066 CGDirectDisplayID display = CGMainDisplayID();
2067 CGRect bounds = CGDisplayBounds(display);
2068
2069 DisplayInfo.X = (int)bounds.origin.x;
2070 DisplayInfo.Y = (int)bounds.origin.y;
2071 DisplayInfo.Width = (int)bounds.size.width;
2072 DisplayInfo.Height = (int)bounds.size.height;
2073 DisplayInfo.Depth = CGDisplayBitsPerPixel(display);
2074 DisplayInfo.ScaleX = 1;
2075 DisplayInfo.ScaleY = 1;
2076}
2077
2078static pascal OSErr HandleQuitMessage(const AppleEvent* ev, AppleEvent* reply, long handlerRefcon) {
2079 Window_Close();
2080 return 0;
2081}
2082
2083static void Window_CommonCreate(void) {
2084 WindowInfo.Exists = true1;
2085 /* for quit buttons in dock and menubar */
2086 AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
2087 NewAEEventHandlerUPP(HandleQuitMessage), 0, false0);
2088}
2089
2090/* Sourced from https://www.meandmark.com/keycodes.html */
2091static const cc_uint8 key_map[8 * 16] = {
2092 'A', 'S', 'D', 'F', 'H', 'G', 'Z', 'X', 'C', 'V', 0, 'B', 'Q', 'W', 'E', 'R',
2093 'Y', 'T', '1', '2', '3', '4', '6', '5', KEY_EQUALS, '9', '7', KEY_MINUS, '8', '0', KEY_RBRACKET, 'O',
2094 'U', KEY_LBRACKET, 'I', 'P', KEY_ENTER, 'L', 'J', KEY_QUOTE, 'K', KEY_SEMICOLON, KEY_BACKSLASH, KEY_COMMA, KEY_SLASH, 'N', 'M', KEY_PERIOD,
2095 KEY_TAB, KEY_SPACE, KEY_TILDE, KEY_BACKSPACE, 0, KEY_ESCAPE, 0, 0, 0, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0,
2096 0, KEY_KP_DECIMAL, 0, KEY_KP_MULTIPLY, 0, KEY_KP_PLUS, 0, KEY_NUMLOCK, 0, 0, 0, KEY_KP_DIVIDE, KEY_KP_ENTER, 0, KEY_KP_MINUS, 0,
2097 0, KEY_KP_ENTER, KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, 0, KEY_KP8, KEY_KP9, 'N', 'M', KEY_PERIOD,
2098 KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11, 0, KEY_F13, 0, KEY_F14, 0, KEY_F10, 0, KEY_F12,
2099 'U', KEY_F15, KEY_INSERT, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END, KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0,
2100};
2101static int MapNativeKey(UInt32 key) { return key < Array_Elems(key_map)(sizeof(key_map) / sizeof(key_map[0])) ? key_map[key] : 0; }
2102/* TODO: Check these.. */
2103/* case 0x37: return KEY_LWIN; */
2104/* case 0x38: return KEY_LSHIFT; */
2105/* case 0x3A: return KEY_LALT; */
2106/* case 0x3B: return Key_ControlLeft; */
2107
2108/* TODO: Verify these differences from OpenTK */
2109/*Backspace = 51, (0x33, KEY_DELETE according to that link)*/
2110/*Return = 52, (0x34, ??? according to that link)*/
2111/*Menu = 110, (0x6E, ??? according to that link)*/
2112
2113/* NOTE: All Pasteboard functions are OSX 10.3 or later */
2114static PasteboardRef Window_GetPasteboard(void) {
2115 PasteboardRef pbRef;
2116 OSStatus err = PasteboardCreate(kPasteboardClipboard, &pbRef);
2117
2118 if (err) Logger_Abort2(err, "Creating Pasteboard reference");
2119 PasteboardSynchronize(pbRef);
2120 return pbRef;
2121}
2122
2123#define FMT_UTF8 CFSTR("public.utf8-plain-text")
2124#define FMT_UTF16 CFSTR("public.utf16-plain-text")
2125
2126void Clipboard_GetText(cc_string* value) {
2127 PasteboardRef pbRef;
2128 ItemCount itemCount;
2129 PasteboardItemID itemID;
2130 CFDataRef outData;
2131 const UInt8* ptr;
2132 CFIndex len;
2133 OSStatus err;
2134
2135 pbRef = Window_GetPasteboard();
2136
2137 err = PasteboardGetItemCount(pbRef, &itemCount);
2138 if (err) Logger_Abort2(err, "Getting item count from Pasteboard");
2139 if (itemCount < 1) return;
2140
2141 err = PasteboardGetItemIdentifier(pbRef, 1, &itemID);
2142 if (err) Logger_Abort2(err, "Getting item identifier from Pasteboard");
2143
2144 if (!(err = PasteboardCopyItemFlavorData(pbRef, itemID, FMT_UTF16, &outData))) {
2145 ptr = CFDataGetBytePtr(outData);
2146 len = CFDataGetLength(outData);
2147 if (ptr) String_AppendUtf16(value, ptr, len);
2148 } else if (!(err = PasteboardCopyItemFlavorData(pbRef, itemID, FMT_UTF8, &outData))) {
2149 ptr = CFDataGetBytePtr(outData);
2150 len = CFDataGetLength(outData);
2151 if (ptr) String_AppendUtf8(value, ptr, len);
2152 }
2153}
2154
2155void Clipboard_SetText(const cc_string* value) {
2156 PasteboardRef pbRef;
2157 CFDataRef cfData;
2158 UInt8 str[800];
2159 int len;
2160 OSStatus err;
2161
2162 pbRef = Window_GetPasteboard();
2163 err = PasteboardClear(pbRef);
2164 if (err) Logger_Abort2(err, "Clearing Pasteboard");
2165 PasteboardSynchronize(pbRef);
2166
2167 len = Platform_EncodeUtf8(str, value);
2168 cfData = CFDataCreate(NULL((void*)0), str, len);
2169 if (!cfData) Logger_Abort("CFDataCreate() returned null pointer");
2170
2171 PasteboardPutItemFlavor(pbRef, 1, FMT_UTF8, cfData, 0);
2172}
2173
2174void Cursor_SetPosition(int x, int y) {
2175 CGPoint point;
2176 point.x = x + windowX;
2177 point.y = y + windowY;
2178 CGDisplayMoveCursorToPoint(CGMainDisplayID(), point);
2179}
2180
2181static void Cursor_DoSetVisible(cc_bool visible) {
2182 if (visible) {
2183 CGDisplayShowCursor(CGMainDisplayID());
2184 } else {
2185 CGDisplayHideCursor(CGMainDisplayID());
2186 }
2187}
2188
2189void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
2190void Window_SetKeyboardText(const cc_string* text) { }
2191void Window_CloseKeyboard(void) { }
2192
2193void Window_EnableRawMouse(void) {
2194 DefaultEnableRawMouse();
2195 CGAssociateMouseAndMouseCursorPosition(0);
2196}
2197
2198void Window_UpdateRawMouse(void) { CentreMousePosition(); }
2199void Window_DisableRawMouse(void) {
2200 CGAssociateMouseAndMouseCursorPosition(1);
2201 DefaultDisableRawMouse();
2202}
2203
2204
2205/*########################################################################################################################*
2206*------------------------------------------------------Carbon window------------------------------------------------------*
2207*#########################################################################################################################*/
2208#if defined CC_BUILD_CARBON
2209#include <Carbon/Carbon.h>
2210
2211static WindowRef win_handle;
2212static cc_bool win_fullscreen, showingDialog;
2213
2214/* fullscreen is tied to OpenGL context unfortunately */
2215static cc_result GLContext_UnsetFullscreen(void);
2216static cc_result GLContext_SetFullscreen(void);
2217
2218static void RefreshWindowBounds(void) {
2219 Rect r;
2220 if (win_fullscreen) return;
2221
2222 /* TODO: kWindowContentRgn ??? */
2223 GetWindowBounds(win_handle, kWindowGlobalPortRgn, &r);
2224 windowX = r.left; WindowInfo.Width = r.right - r.left;
2225 windowY = r.top; WindowInfo.Height = r.bottom - r.top;
2226}
2227
2228static OSStatus Window_ProcessKeyboardEvent(EventRef inEvent) {
2229 UInt32 kind, code;
2230 int key;
2231 OSStatus res;
2232
2233 kind = GetEventKind(inEvent);
2234 switch (kind) {
2235 case kEventRawKeyDown:
2236 case kEventRawKeyRepeat:
2237 case kEventRawKeyUp:
2238 res = GetEventParameter(inEvent, kEventParamKeyCode, typeUInt32,
2239 NULL((void*)0), sizeof(UInt32), NULL((void*)0), &code);
2240 if (res) Logger_Abort2(res, "Getting key button");
2241
2242 key = MapNativeKey(code);
2243 if (key) {
2244 Input_Set(key, kind != kEventRawKeyUp);
2245 } else {
2246 Platform_Log1("Ignoring unknown key %i", &code);
2247 }
2248 return eventNotHandledErr;
2249
2250 case kEventRawKeyModifiersChanged:
2251 res = GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32,
2252 NULL((void*)0), sizeof(UInt32), NULL((void*)0), &code);
2253 if (res) Logger_Abort2(res, "Getting key modifiers");
2254
2255 Input_Set(KEY_LCTRL, code & 0x1000);
2256 Input_Set(KEY_LALT, code & 0x0800);
2257 Input_Set(KEY_LSHIFT, code & 0x0200);
2258 Input_Set(KEY_LWIN, code & 0x0100);
2259 Input_Set(KEY_CAPSLOCK, code & 0x0400);
2260 return eventNotHandledErr;
2261 }
2262 return eventNotHandledErr;
2263}
2264
2265static OSStatus Window_ProcessWindowEvent(EventRef inEvent) {
2266 int oldWidth, oldHeight;
2267
2268 switch (GetEventKind(inEvent)) {
2269 case kEventWindowClose:
2270 WindowInfo.Exists = false0;
2271 Event_RaiseVoid(&WindowEvents.Closing);
2272 return eventNotHandledErr;
2273
2274 case kEventWindowClosed:
2275 WindowInfo.Exists = false0;
2276 return 0;
2277
2278 case kEventWindowBoundsChanged:
2279 oldWidth = WindowInfo.Width; oldHeight = WindowInfo.Height;
2280 RefreshWindowBounds();
2281
2282 if (oldWidth != WindowInfo.Width || oldHeight != WindowInfo.Height) {
2283 Event_RaiseVoid(&WindowEvents.Resized);
2284 }
2285 return eventNotHandledErr;
2286
2287 case kEventWindowActivated:
2288 WindowInfo.Focused = true1;
2289 Event_RaiseVoid(&WindowEvents.FocusChanged);
2290 return eventNotHandledErr;
2291
2292 case kEventWindowDeactivated:
2293 WindowInfo.Focused = false0;
2294 Event_RaiseVoid(&WindowEvents.FocusChanged);
2295 return eventNotHandledErr;
2296
2297 case kEventWindowDrawContent:
2298 Event_RaiseVoid(&WindowEvents.Redraw);
2299 return eventNotHandledErr;
2300 }
2301 return eventNotHandledErr;
2302}
2303
2304static OSStatus Window_ProcessMouseEvent(EventRef inEvent) {
2305 int mouseX, mouseY;
2306 HIPoint pt, raw;
2307 UInt32 kind;
2308 cc_bool down;
2309 EventMouseButton button;
2310 SInt32 delta;
2311 OSStatus res;
2312
2313 res = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
2314 NULL((void*)0), sizeof(HIPoint), NULL((void*)0), &pt);
2315 /* this error comes up from the application event handler */
2316 if (res && res != eventParameterNotFoundErr) {
2317 Logger_Abort2(res, "Getting mouse position");
2318 }
2319
2320 if (Input_RawMode) {
2321 raw.x = 0; raw.y = 0;
2322 GetEventParameter(inEvent, kEventParamMouseDelta, typeHIPoint, NULL((void*)0), sizeof(HIPoint), NULL((void*)0), &raw);
2323 Event_RaiseRawMove(&PointerEvents.RawMoved, raw.x, raw.y);
2324 }
2325
2326 if (showingDialog) return eventNotHandledErr;
2327 mouseX = (int)pt.x; mouseY = (int)pt.y;
2328
2329 /* kEventParamMouseLocation is in screen coordinates */
2330 /* So need to make sure mouse click lies inside window */
2331 /* Otherwise this breaks close/minimise/maximise in titlebar and dock */
2332 if (!win_fullscreen) {
2333 mouseX -= windowX; mouseY -= windowY;
2334
2335 if (mouseX < 0 || mouseX >= WindowInfo.Width) return eventNotHandledErr;
2336 if (mouseY < 0 || mouseY >= WindowInfo.Height) return eventNotHandledErr;
2337 }
2338
2339 kind = GetEventKind(inEvent);
2340 switch (kind) {
2341 case kEventMouseDown:
2342 case kEventMouseUp:
2343 down = kind == kEventMouseDown;
2344 res = GetEventParameter(inEvent, kEventParamMouseButton, typeMouseButton,
2345 NULL((void*)0), sizeof(EventMouseButton), NULL((void*)0), &button);
2346 if (res) Logger_Abort2(res, "Getting mouse button");
2347
2348 switch (button) {
2349 case kEventMouseButtonPrimary:
2350 Input_Set(KEY_LMOUSE, down); break;
2351 case kEventMouseButtonSecondary:
2352 Input_Set(KEY_RMOUSE, down); break;
2353 case kEventMouseButtonTertiary:
2354 Input_Set(KEY_MMOUSE, down); break;
2355 }
2356 return eventNotHandledErr;
2357
2358 case kEventMouseWheelMoved:
2359 res = GetEventParameter(inEvent, kEventParamMouseWheelDelta, typeSInt32,
2360 NULL((void*)0), sizeof(SInt32), NULL((void*)0), &delta);
2361 if (res) Logger_Abort2(res, "Getting mouse wheel delta");
2362 Mouse_ScrollWheel(delta);
2363 return 0;
2364
2365 case kEventMouseMoved:
2366 case kEventMouseDragged:
2367 Pointer_SetPosition(0, mouseX, mouseY);
2368 return eventNotHandledErr;
2369 }
2370 return eventNotHandledErr;
2371}
2372
2373static OSStatus Window_ProcessTextEvent(EventRef inEvent) {
2374 UInt32 kind;
2375 EventRef keyEvent = NULL((void*)0);
2376 UniChar chars[17] = { 0 };
2377 char keyChar;
2378 int i;
2379 OSStatus res;
2380
2381 kind = GetEventKind(inEvent);
2382 if (kind != kEventTextInputUnicodeForKeyEvent) return eventNotHandledErr;
2383
2384 /* When command key is pressed, text input should be ignored */
2385 res = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent,
2386 typeEventRef, NULL((void*)0), sizeof(keyEvent), NULL((void*)0), &keyEvent);
2387 if (!res && keyEvent) {
2388 UInt32 modifiers = 0;
2389 GetEventParameter(keyEvent, kEventParamKeyModifiers,
2390 typeUInt32, NULL((void*)0), sizeof(modifiers), NULL((void*)0), &modifiers);
2391 if (modifiers & 0x0100) return eventNotHandledErr;
2392 }
2393
2394 /* TODO: is the assumption we only get 1-4 characters always valid */
2395 res = GetEventParameter(inEvent, kEventParamTextInputSendText,
2396 typeUnicodeText, NULL((void*)0), 16 * sizeof(UniChar), NULL((void*)0), chars);
2397 if (res) Logger_Abort2(res, "Getting text chars");
2398
2399 for (i = 0; i < 16 && chars[i]; i++) {
2400 /* TODO: UTF16 to codepoint conversion */
2401 if (Convert_TryCodepointToCP437(chars[i], &keyChar)) {
2402 Event_RaiseInt(&InputEvents.Press, keyChar);
2403 }
2404 }
2405 return eventNotHandledErr;
2406}
2407
2408static OSStatus Window_EventHandler(EventHandlerCallRef inCaller, EventRef inEvent, void* userData) {
2409 EventRecord record;
2410
2411 switch (GetEventClass(inEvent)) {
2412 case kEventClassAppleEvent:
2413 /* Only event here is the apple event. */
2414 Platform_LogConst("Processing apple event.");
2415 ConvertEventRefToEventRecord(inEvent, &record);
2416 AEProcessAppleEvent(&record);
2417 break;
2418
2419 case kEventClassKeyboard:
2420 return Window_ProcessKeyboardEvent(inEvent);
2421 case kEventClassMouse:
2422 return Window_ProcessMouseEvent(inEvent);
2423 case kEventClassWindow:
2424 return Window_ProcessWindowEvent(inEvent);
2425 case kEventClassTextInput:
2426 return Window_ProcessTextEvent(inEvent);
2427 }
2428 return eventNotHandledErr;
2429}
2430
2431typedef EventTargetRef (*GetMenuBarEventTarget_Func)(void);
2432
2433static void HookEvents(void) {
2434 static const EventTypeSpec winEventTypes[] = {
2435 { kEventClassKeyboard, kEventRawKeyDown },
2436 { kEventClassKeyboard, kEventRawKeyRepeat },
2437 { kEventClassKeyboard, kEventRawKeyUp },
2438 { kEventClassKeyboard, kEventRawKeyModifiersChanged },
2439
2440 { kEventClassWindow, kEventWindowClose },
2441 { kEventClassWindow, kEventWindowClosed },
2442 { kEventClassWindow, kEventWindowBoundsChanged },
2443 { kEventClassWindow, kEventWindowActivated },
2444 { kEventClassWindow, kEventWindowDeactivated },
2445 { kEventClassWindow, kEventWindowDrawContent },
2446
2447 { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent },
2448 { kEventClassAppleEvent, kEventAppleEvent }
2449 };
2450 static const EventTypeSpec appEventTypes[] = {
2451 { kEventClassMouse, kEventMouseDown },
2452 { kEventClassMouse, kEventMouseUp },
2453 { kEventClassMouse, kEventMouseMoved },
2454 { kEventClassMouse, kEventMouseDragged },
2455 { kEventClassMouse, kEventMouseWheelMoved }
2456 };
2457 GetMenuBarEventTarget_Func getMenuBarEventTarget;
2458 EventTargetRef target;
2459
2460 target = GetWindowEventTarget(win_handle);
2461 InstallEventHandler(target, NewEventHandlerUPP(Window_EventHandler),
2462 Array_Elems(winEventTypes)(sizeof(winEventTypes) / sizeof(winEventTypes[0])), winEventTypes, NULL((void*)0), NULL((void*)0));
2463
2464 target = GetApplicationEventTarget();
2465 InstallEventHandler(target, NewEventHandlerUPP(Window_EventHandler),
2466 Array_Elems(appEventTypes)(sizeof(appEventTypes) / sizeof(appEventTypes[0])), appEventTypes, NULL((void*)0), NULL((void*)0));
2467
2468 /* The code below is to get the menubar working. */
2469 /* The documentation for 'RunApplicationEventLoop' states that it installs */
2470 /* the standard application event handler which lets the menubar work. */
2471 /* However, we cannot use that since the event loop is managed by us instead. */
2472 /* Unfortunately, there is no proper API to duplicate that behaviour, so reply */
2473 /* on the undocumented GetMenuBarEventTarget to achieve similar behaviour. */
2474 /* TODO: This is wrong for PowerPC. But at least it doesn't crash or anything. */
2475#define _RTLD_DEFAULT ((void*)-2)
2476 getMenuBarEventTarget = DynamicLib_Get2(_RTLD_DEFAULT, "GetMenuBarEventTarget");
2477 InstallStandardEventHandler(GetApplicationEventTarget());
2478
2479 /* TODO: Why does this not work properly until user activates another window */
2480 /* Followup: Seems to be a bug in OSX http://www.sheepsystems.com/developers_blog/transformprocesstype--bette.html */
2481 if (getMenuBarEventTarget) {
2482 InstallStandardEventHandler(getMenuBarEventTarget());
2483 } else {
2484 Platform_LogConst("MenuBar won't work!");
2485 }
2486 /* MenuRef menu = AcquireRootMenu(); */
2487 /* InstallStandardEventHandler(GetMenuEventTarget(menu)); */
2488}
2489
2490
2491/*########################################################################################################################*
2492 *--------------------------------------------------Public implementation--------------------------------------------------*
2493 *#########################################################################################################################*/
2494void Window_Init(void) { Window_CommonInit(); }
2495
2496/* Private CGS/CGL stuff */
2497typedef int CGSConnectionID;
2498typedef int CGSWindowID;
2499extern CGSConnectionID _CGSDefaultConnection(void);
2500extern CGSWindowID GetNativeWindowFromWindowRef(WindowRef window);
2501extern CGContextRef CGWindowContextCreate(CGSConnectionID conn, CGSWindowID win, void* opts);
2502
2503static CGSConnectionID conn;
2504static CGSWindowID winId;
2505
2506#ifdef CC_BUILD_ICON
2507extern const int CCIcon_Data[];
2508extern const int CCIcon_Width, CCIcon_Height;
2509
2510static void ApplyIcon(void) {
2511 CGColorSpaceRef colSpace;
2512 CGDataProviderRef provider;
2513 CGImageRef image;
2514
2515 colSpace = CGColorSpaceCreateDeviceRGB();
2516 provider = CGDataProviderCreateWithData(NULL((void*)0), CCIcon_Data,
2517 Bitmap_DataSize(CCIcon_Width, CCIcon_Height)((cc_uint32)(CCIcon_Width) * (cc_uint32)(CCIcon_Height) * 4), NULL((void*)0));
2518 image = CGImageCreate(CCIcon_Width, CCIcon_Height, 8, 32, CCIcon_Width * 4, colSpace,
2519 kCGBitmapByteOrder32Little | kCGImageAlphaLast, provider, NULL((void*)0), 0, 0);
2520
2521 SetApplicationDockTileImage(image);
2522 CGImageRelease(image);
2523 CGDataProviderRelease(provider);
2524 CGColorSpaceRelease(colSpace);
2525}
2526#else
2527static void ApplyIcon(void) { }
2528#endif
2529
2530void Window_Create(int width, int height) {
2531 Rect r;
2532 OSStatus res;
2533 ProcessSerialNumber psn;
2534
2535 r.left = Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2); r.right = r.left + width;
2536 r.top = Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2); r.bottom = r.top + height;
2537 res = CreateNewWindow(kDocumentWindowClass,
2538 kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute |
2539 kWindowInWindowMenuAttribute | kWindowLiveResizeAttribute, &r, &win_handle);
2540
2541 if (res) Logger_Abort2(res, "Creating window failed");
2542 RefreshWindowBounds();
2543
2544 AcquireRootMenu();
2545 GetCurrentProcess(&psn);
2546 SetFrontProcess(&psn);
2547
2548 /* TODO: Use BringWindowToFront instead.. (look in the file which has RepositionWindow in it) !!!! */
2549 HookEvents();
2550 Window_CommonCreate();
2551 WindowInfo.Handle = win_handle;
2552
2553 conn = _CGSDefaultConnection();
2554 winId = GetNativeWindowFromWindowRef(win_handle);
2555 ApplyIcon();
2556}
2557
2558void Window_SetTitle(const cc_string* title) {
2559 UInt8 str[NATIVE_STR_LEN600];
2560 CFStringRef titleCF;
2561 int len;
2562
2563 /* TODO: This leaks memory, old title isn't released */
2564 len = Platform_EncodeUtf8(str, title);
2565 titleCF = CFStringCreateWithBytes(kCFAllocatorDefault, str, len, kCFStringEncodingUTF8, false0);
2566 SetWindowTitleWithCFString(win_handle, titleCF);
2567}
2568
2569void Window_Show(void) {
2570 ShowWindow(win_handle);
2571 /* TODO: Do we actually need to reposition */
2572 RepositionWindow(win_handle, NULL((void*)0), kWindowCenterOnMainScreen);
2573 SelectWindow(win_handle);
2574}
2575
2576int Window_GetWindowState(void) {
2577 if (win_fullscreen) return WINDOW_STATE_FULLSCREEN;
2578 if (IsWindowCollapsed(win_handle)) return WINDOW_STATE_MINIMISED;
2579 return WINDOW_STATE_NORMAL;
2580}
2581
2582static void UpdateWindowState(void) {
2583 Event_RaiseVoid(&WindowEvents.StateChanged);
2584 RefreshWindowBounds();
2585 Event_RaiseVoid(&WindowEvents.Resized);
2586}
2587
2588cc_result Window_EnterFullscreen(void) {
2589 cc_result res = GLContext_SetFullscreen();
2590 UpdateWindowState();
2591 return res;
2592}
2593cc_result Window_ExitFullscreen(void) {
2594 cc_result res = GLContext_UnsetFullscreen();
2595 UpdateWindowState();
2596 return res;
2597}
2598
2599void Window_SetSize(int width, int height) {
2600 SizeWindow(win_handle, width, height, true1);
2601}
2602
2603void Window_Close(void) {
2604 /* DisposeWindow only sends a kEventWindowClosed */
2605 Event_RaiseVoid(&WindowEvents.Closing);
2606 if (WindowInfo.Exists) DisposeWindow(win_handle);
2607 WindowInfo.Exists = false0;
2608}
2609
2610void Window_ProcessEvents(void) {
2611 EventRef theEvent;
2612 EventTargetRef target = GetEventDispatcherTarget();
2613 OSStatus res;
2614
2615 for (;;) {
2616 res = ReceiveNextEvent(0, NULL((void*)0), 0.0, true1, &theEvent);
2617 if (res == eventLoopTimedOutErr) break;
2618
2619 if (res) {
2620 Platform_Log1("Message Loop status: %i", &res); break;
2621 }
2622 if (!theEvent) break;
2623
2624 SendEventToEventTarget(theEvent, target);
2625 ReleaseEvent(theEvent);
2626 }
2627}
2628
2629static void Cursor_GetRawPos(int* x, int* y) {
2630 Point point;
2631 GetGlobalMouse(&point);
2632 *x = (int)point.h; *y = (int)point.v;
2633}
2634
2635static void ShowDialogCore(const char* title, const char* msg) {
2636 CFStringRef titleCF = CFStringCreateWithCString(NULL((void*)0), title, kCFStringEncodingASCII);
2637 CFStringRef msgCF = CFStringCreateWithCString(NULL((void*)0), msg, kCFStringEncodingASCII);
2638 DialogRef dialog;
2639 DialogItemIndex itemHit;
2640
2641 showingDialog = true1;
2642 CreateStandardAlert(kAlertPlainAlert, titleCF, msgCF, NULL((void*)0), &dialog);
2643 CFRelease(titleCF);
2644 CFRelease(msgCF);
2645
2646 RunStandardAlert(dialog, NULL((void*)0), &itemHit);
2647 showingDialog = false0;
2648}
2649
2650static CGrafPtr fb_port;
2651static struct Bitmap fb_bmp;
2652static CGColorSpaceRef colorSpace;
2653
2654void Window_AllocFramebuffer(struct Bitmap* bmp) {
2655 if (!fb_port) fb_port = GetWindowPort(win_handle);
2656
2657 bmp->scan0 = Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
2658 colorSpace = CGColorSpaceCreateDeviceRGB();
2659 fb_bmp = *bmp;
2660}
2661
2662void Window_DrawFramebuffer(Rect2D r) {
2663 CGContextRef context = NULL((void*)0);
2664 CGDataProviderRef provider;
2665 CGImageRef image;
2666 CGRect rect;
2667 OSStatus err;
2668
2669 /* Unfortunately CGImageRef is immutable, so changing the */
2670 /* underlying data doesn't change what shows when drawing. */
2671 /* TODO: Use QuickDraw alternative instead */
2672
2673 /* TODO: Only update changed bit.. */
2674 rect.origin.x = 0; rect.origin.y = 0;
2675 rect.size.width = WindowInfo.Width;
2676 rect.size.height = WindowInfo.Height;
2677
2678 err = QDBeginCGContext(fb_port, &context);
2679 if (err) Logger_Abort2(err, "Begin draw");
2680 /* TODO: REPLACE THIS AWFUL HACK */
2681
2682 provider = CGDataProviderCreateWithData(NULL((void*)0), fb_bmp.scan0,
2683 Bitmap_DataSize(fb_bmp.width, fb_bmp.height)((cc_uint32)(fb_bmp.width) * (cc_uint32)(fb_bmp.height) * 4), NULL((void*)0));
2684#ifdef CC_BIG_ENDIAN
2685 image = CGImageCreate(fb_bmp.width, fb_bmp.height, 8, 32, fb_bmp.width * 4, colorSpace,
2686 kCGImageAlphaNoneSkipFirst, provider, NULL((void*)0), 0, 0);
2687#else
2688 image = CGImageCreate(fb_bmp.width, fb_bmp.height, 8, 32, fb_bmp.width * 4, colorSpace,
2689 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, provider, NULL((void*)0), 0, 0);
2690#endif
2691
2692 CGContextDrawImage(context, rect, image);
2693 CGContextSynchronize(context);
2694 err = QDEndCGContext(fb_port, &context);
2695 if (err) Logger_Abort2(err, "End draw");
2696
2697 CGImageRelease(image);
2698 CGDataProviderRelease(provider);
2699}
2700
2701void Window_FreeFramebuffer(struct Bitmap* bmp) {
2702 Mem_Free(bmp->scan0);
2703 CGColorSpaceRelease(colorSpace);
2704}
2705
2706
2707/*########################################################################################################################*
2708*-------------------------------------------------------Cocoa window------------------------------------------------------*
2709*#########################################################################################################################*/
2710#elif defined CC_BUILD_COCOA
2711#include <objc/message.h>
2712#include <objc/runtime.h>
2713static id appHandle, winHandle, viewHandle;
2714extern void* NSDefaultRunLoopMode;
2715
2716static SEL selFrame, selDeltaX, selDeltaY;
2717static SEL selNextEvent, selType, selSendEvent;
2718static SEL selButton, selKeycode, selModifiers;
2719static SEL selCharacters, selUtf8String, selMouseLoc;
2720static SEL selCurrentContext, selGraphicsPort;
2721static SEL selSetNeedsDisplay, selDisplayIfNeeded;
2722static SEL selUpdate, selFlushBuffer;
2723
2724static void RegisterSelectors(void) {
2725 selFrame = sel_registerName("frame");
2726 selDeltaX = sel_registerName("deltaX");
2727 selDeltaY = sel_registerName("deltaY");
2728
2729 selNextEvent = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:");
2730 selType = sel_registerName("type");
2731 selSendEvent = sel_registerName("sendEvent:");
2732
2733 selButton = sel_registerName("buttonNumber");
2734 selKeycode = sel_registerName("keyCode");
2735 selModifiers = sel_registerName("modifierFlags");
2736
2737 selCharacters = sel_registerName("characters");
2738 selUtf8String = sel_registerName("UTF8String");
2739 selMouseLoc = sel_registerName("mouseLocation");
2740
2741 selCurrentContext = sel_registerName("currentContext");
2742 selGraphicsPort = sel_registerName("graphicsPort");
2743 selSetNeedsDisplay = sel_registerName("setNeedsDisplayInRect:");
2744 selDisplayIfNeeded = sel_registerName("displayIfNeeded");
2745
2746 selUpdate = sel_registerName("update");
2747 selFlushBuffer = sel_registerName("flushBuffer");
2748}
2749
2750static CC_INLINEinline CGFloat Send_CGFloat(id receiver, SEL sel) {
2751 /* Sometimes we have to use fpret and sometimes we don't. See this for more details: */
2752 /* http://www.sealiesoftware.com/blog/archive/2008/11/16/objc_explain_objc_msgSend_fpret.html */
2753 /* return type is void*, but we cannot cast a void* to a float or double */
2754
2755#ifdef __i386__
2756 return ((CGFloat(*)(id, SEL))(void *)objc_msgSend_fpret)(receiver, sel);
2757#else
2758 return ((CGFloat(*)(id, SEL))(void *)objc_msgSend)(receiver, sel);
2759#endif
2760}
2761
2762static CC_INLINEinline CGPoint Send_CGPoint(id receiver, SEL sel) {
2763 /* on x86 and x86_64 CGPoint fits the requirements for 'struct returned in registers' */
2764 return ((CGPoint(*)(id, SEL))(void *)objc_msgSend)(receiver, sel);
2765}
2766
2767static void RefreshWindowBounds(void) {
2768 CGRect win, view;
2769 int viewY;
2770
2771 win = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(winHandle, selFrame);
2772 view = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(viewHandle, selFrame);
2773
2774 /* For cocoa, the 0,0 origin is the bottom left corner of windows/views/screen. */
2775 /* To get window's real Y screen position, first need to find Y of top. (win.y + win.height) */
2776 /* Then just subtract from screen height to make relative to top instead of bottom of the screen. */
2777 /* Of course this is only half the story, since we're really after Y position of the content. */
2778 /* To work out top Y of view relative to window, it's just win.height - (view.y + view.height) */
2779 viewY = (int)win.size.height - ((int)view.origin.y + (int)view.size.height);
2780 windowX = (int)win.origin.x + (int)view.origin.x;
2781 windowY = DisplayInfo.Height - ((int)win.origin.y + (int)win.size.height) + viewY;
2782
2783 WindowInfo.Width = (int)view.size.width;
2784 WindowInfo.Height = (int)view.size.height;
2785}
2786
2787static void OnDidResize(id self, SEL cmd, id notification) {
2788 RefreshWindowBounds();
2789 Event_RaiseVoid(&WindowEvents.Resized);
2790}
2791
2792static void OnDidMove(id self, SEL cmd, id notification) {
2793 RefreshWindowBounds();
2794 GLContext_Update();
2795}
2796
2797static void OnDidBecomeKey(id self, SEL cmd, id notification) {
2798 WindowInfo.Focused = true1;
2799 Event_RaiseVoid(&WindowEvents.FocusChanged);
2800}
2801
2802static void OnDidResignKey(id self, SEL cmd, id notification) {
2803 WindowInfo.Focused = false0;
2804 Event_RaiseVoid(&WindowEvents.FocusChanged);
2805}
2806
2807static void OnDidMiniaturize(id self, SEL cmd, id notification) {
2808 Event_RaiseVoid(&WindowEvents.StateChanged);
2809}
2810
2811static void OnDidDeminiaturize(id self, SEL cmd, id notification) {
2812 Event_RaiseVoid(&WindowEvents.StateChanged);
2813}
2814
2815static void OnWillClose(id self, SEL cmd, id notification) {
2816 WindowInfo.Exists = false0;
2817 Event_RaiseVoid(&WindowEvents.Closing);
2818}
2819
2820/* If this isn't overriden, an annoying beep sound plays anytime a key is pressed */
2821static void OnKeyDown(id self, SEL cmd, id ev) { }
2822
2823static Class Window_MakeClass(void) {
2824 Class c = objc_allocateClassPair(objc_getClass("NSWindow"), "ClassiCube_Window", 0);
2825
2826 class_addMethod(c, sel_registerName("windowDidResize:"), OnDidResize, "v@:@");
2827 class_addMethod(c, sel_registerName("windowDidMove:"), OnDidMove, "v@:@");
2828 class_addMethod(c, sel_registerName("windowDidBecomeKey:"), OnDidBecomeKey, "v@:@");
2829 class_addMethod(c, sel_registerName("windowDidResignKey:"), OnDidResignKey, "v@:@");
2830 class_addMethod(c, sel_registerName("windowDidMiniaturize:"), OnDidMiniaturize, "v@:@");
2831 class_addMethod(c, sel_registerName("windowDidDeminiaturize:"), OnDidDeminiaturize, "v@:@");
2832 class_addMethod(c, sel_registerName("windowWillClose:"), OnWillClose, "v@:@");
2833 class_addMethod(c, sel_registerName("keyDown:"), OnKeyDown, "v@:@");
2834
2835 objc_registerClassPair(c);
2836 return c;
2837}
2838
2839/* When the user users left mouse to drag reisze window, this enters 'live resize' mode */
2840/* Although the game receives a left mouse down event, it does NOT receive a left mouse up */
2841/* This causes the game to get stuck with left mouse down after user finishes resizing */
2842/* So work arond that by always releasing left mouse when a live resize is finished */
2843static void DidEndLiveResize(id self, SEL cmd) {
2844 Input_SetReleased(KEY_LMOUSE);
2845}
2846
2847static void View_DrawRect(id self, SEL cmd, CGRect r);
2848static void MakeContentView(void) {
2849 CGRect rect;
2850 id view;
2851 Class c;
2852
2853 view = objc_msgSend(winHandle, sel_registerName("contentView"));
2854 rect = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(view, selFrame);
2855
2856 c = objc_allocateClassPair(objc_getClass("NSView"), "ClassiCube_View", 0);
2857 // TODO: test rect is actually correct in View_DrawRect on both 32 and 64 bit
2858#ifdef __i386__
2859 class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
2860#else
2861 class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=dd}{NSSize=dd}}");
2862#endif
2863 class_addMethod(c, sel_registerName("viewDidEndLiveResize"), DidEndLiveResize, "v@:");
2864 objc_registerClassPair(c);
2865
2866 viewHandle = objc_msgSend(c, sel_registerName("alloc"));
2867 objc_msgSend(viewHandle, sel_registerName("initWithFrame:"), rect);
2868 objc_msgSend(winHandle, sel_registerName("setContentView:"), viewHandle);
2869}
2870
2871void Window_Init(void) {
2872 appHandle = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
2873 objc_msgSend(appHandle, sel_registerName("activateIgnoringOtherApps:"), true1);
2874 Window_CommonInit();
2875 RegisterSelectors();
2876}
2877
2878#ifdef CC_BUILD_ICON
2879extern const int CCIcon_Data[];
2880extern const int CCIcon_Width, CCIcon_Height;
2881
2882static void ApplyIcon(void) {
2883 CGColorSpaceRef colSpace;
2884 CGDataProviderRef provider;
2885 CGImageRef image;
2886 CGSize size;
2887 void* img;
2888
2889 colSpace = CGColorSpaceCreateDeviceRGB();
2890 provider = CGDataProviderCreateWithData(NULL((void*)0), CCIcon_Data,
2891 Bitmap_DataSize(CCIcon_Width, CCIcon_Height)((cc_uint32)(CCIcon_Width) * (cc_uint32)(CCIcon_Height) * 4), NULL((void*)0));
2892 image = CGImageCreate(CCIcon_Width, CCIcon_Height, 8, 32, CCIcon_Width * 4, colSpace,
2893 kCGBitmapByteOrder32Little | kCGImageAlphaLast, provider, NULL((void*)0), 0, 0);
2894
2895 size.width = 0; size.height = 0;
2896 img = objc_msgSend((id)objc_getClass("NSImage"), sel_registerName("alloc"));
2897 objc_msgSend(img, sel_registerName("initWithCGImage:size:"), image, size);
2898 objc_msgSend(appHandle, sel_registerName("setApplicationIconImage:"), img);
2899
2900 /* TODO need to release NSImage here */
2901 CGImageRelease(image);
2902 CGDataProviderRelease(provider);
2903 CGColorSpaceRelease(colSpace);
2904}
2905#else
2906static void ApplyIcon(void) { }
2907#endif
2908
2909#define NSTitledWindowMask (1 << 0)
2910#define NSClosableWindowMask (1 << 1)
2911#define NSMiniaturizableWindowMask (1 << 2)
2912#define NSResizableWindowMask (1 << 3)
2913#define NSFullScreenWindowMask (1 << 14)
2914
2915void Window_Create(int width, int height) {
2916 Class winClass;
2917 CGRect rect;
2918
2919 /* Technically the coordinates for the origin are at bottom left corner */
2920 /* But since the window is in centre of the screen, don't need to care here */
2921 rect.origin.x = Display_CentreX(width)(DisplayInfo.X + (DisplayInfo.Width - width) / 2);
2922 rect.origin.y = Display_CentreY(height)(DisplayInfo.Y + (DisplayInfo.Height - height) / 2);
2923 rect.size.width = width;
2924 rect.size.height = height;
2925
2926 winClass = Window_MakeClass();
2927 winHandle = objc_msgSend(winClass, sel_registerName("alloc"));
2928 objc_msgSend(winHandle, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask), 0, false0);
2929
2930 Window_CommonCreate();
2931 objc_msgSend(winHandle, sel_registerName("setDelegate:"), winHandle);
2932 RefreshWindowBounds();
2933 MakeContentView();
2934 ApplyIcon();
2935}
2936
2937void Window_SetTitle(const cc_string* title) {
2938 UInt8 str[NATIVE_STR_LEN600];
2939 CFStringRef titleCF;
2940 int len;
2941
2942 /* TODO: This leaks memory, old title isn't released */
2943 len = Platform_EncodeUtf8(str, title);
2944 titleCF = CFStringCreateWithBytes(kCFAllocatorDefault, str, len, kCFStringEncodingUTF8, false0);
2945 objc_msgSend(winHandle, sel_registerName("setTitle:"), titleCF);
2946}
2947
2948void Window_Show(void) {
2949 objc_msgSend(winHandle, sel_registerName("makeKeyAndOrderFront:"), appHandle);
2950 RefreshWindowBounds(); // TODO: even necessary?
2951}
2952
2953int Window_GetWindowState(void) {
2954 int flags;
2955
2956 flags = (int)objc_msgSend(winHandle, sel_registerName("styleMask"));
2957 if (flags & NSFullScreenWindowMask) return WINDOW_STATE_FULLSCREEN;
2958
2959 flags = (int)objc_msgSend(winHandle, sel_registerName("isMiniaturized"));
2960 return flags ? WINDOW_STATE_MINIMISED : WINDOW_STATE_NORMAL;
2961}
2962
2963// TODO: Only works on 10.7+
2964cc_result Window_EnterFullscreen(void) {
2965 objc_msgSend(winHandle, sel_registerName("toggleFullScreen:"), appHandle);
2966 return 0;
2967}
2968cc_result Window_ExitFullscreen(void) {
2969 objc_msgSend(winHandle, sel_registerName("toggleFullScreen:"), appHandle);
2970 return 0;
2971}
2972
2973void Window_SetSize(int width, int height) {
2974 /* Can't use setContentSize:, because that resizes from the bottom left corner. */
2975 CGRect rect = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(winHandle, selFrame);
2976
2977 rect.origin.y += WindowInfo.Height - height;
2978 rect.size.width += width - WindowInfo.Width;
2979 rect.size.height += height - WindowInfo.Height;
2980 objc_msgSend(winHandle, sel_registerName("setFrame:display:"), rect, true1);
2981}
2982
2983void Window_Close(void) {
2984 objc_msgSend(winHandle, sel_registerName("close"));
2985}
2986
2987static int MapNativeMouse(int button) {
2988 if (button == 0) return KEY_LMOUSE;
2989 if (button == 1) return KEY_RMOUSE;
2990 if (button == 2) return KEY_MMOUSE;
2991 return 0;
2992}
2993
2994static void ProcessKeyChars(id ev) {
2995 char buffer[128];
2996 const char* src;
2997 cc_string str;
2998 id chars;
2999 int i, len, flags;
3000
3001 /* Ignore text input while cmd is held down */
3002 /* e.g. so Cmd + V to paste doesn't leave behind 'v' */
3003 flags = (int)objc_msgSend(ev, selModifiers);
3004 if (flags & 0x000008) return;
3005 if (flags & 0x000010) return;
3006
3007 chars = objc_msgSend(ev, selCharacters);
3008 src = objc_msgSend(chars, selUtf8String);
3009 len = String_Length(src);
3010 String_InitArray(str, buffer)str.buffer = buffer; str.length = 0; str.capacity = sizeof(buffer
);
;
3011
3012 String_AppendUtf8(&str, src, len);
3013 for (i = 0; i < str.length; i++) {
3014 Event_RaiseInt(&InputEvents.Press, str.buffer[i]);
3015 }
3016}
3017
3018static cc_bool GetMouseCoords(int* x, int* y) {
3019 CGPoint loc = Send_CGPoint((id)objc_getClass("NSEvent"), selMouseLoc);
3020 *x = (int)loc.x - windowX;
3021 *y = (DisplayInfo.Height - (int)loc.y) - windowY;
3022 // TODO: this seems to be off by 1
3023 return *x >= 0 && *y >= 0 && *x < WindowInfo.Width && *y < WindowInfo.Height;
3024}
3025
3026static int TryGetKey(id ev) {
3027 int code = (int)objc_msgSend(ev, selKeycode);
3028 int key = MapNativeKey(code);
3029 if (key) return key;
3030
3031 Platform_Log1("Unknown key %i", &code);
3032 return 0;
3033}
3034
3035void Window_ProcessEvents(void) {
3036 id ev;
3037 int key, type, steps, x, y;
3038 CGFloat dx, dy;
3039
3040 for (;;) {
3041 ev = objc_msgSend(appHandle, selNextEvent, 0xFFFFFFFFU, NULL((void*)0), NSDefaultRunLoopMode, true1);
3042 if (!ev) break;
3043 type = (int)objc_msgSend(ev, selType);
3044
3045 switch (type) {
3046 case 1: /* NSLeftMouseDown */
3047 case 3: /* NSRightMouseDown */
3048 case 25: /* NSOtherMouseDown */
3049 key = MapNativeMouse((int)objc_msgSend(ev, selButton));
3050 if (GetMouseCoords(&x, &y) && key) Input_SetPressed(key);
3051 break;
3052
3053 case 2: /* NSLeftMouseUp */
3054 case 4: /* NSRightMouseUp */
3055 case 26: /* NSOtherMouseUp */
3056 key = MapNativeMouse((int)objc_msgSend(ev, selButton));
3057 if (key) Input_SetReleased(key);
3058 break;
3059
3060 case 10: /* NSKeyDown */
3061 key = TryGetKey(ev);
3062 if (key) Input_SetPressed(key);
3063 // TODO: Test works properly with other languages
3064 ProcessKeyChars(ev);
3065 break;
3066
3067 case 11: /* NSKeyUp */
3068 key = TryGetKey(ev);
3069 if (key) Input_SetReleased(key);
3070 break;
3071
3072 case 12: /* NSFlagsChanged */
3073 key = (int)objc_msgSend(ev, selModifiers);
3074 /* TODO: Figure out how to only get modifiers that changed */
3075 Input_Set(KEY_LCTRL, key & 0x000001);
3076 Input_Set(KEY_LSHIFT, key & 0x000002);
3077 Input_Set(KEY_RSHIFT, key & 0x000004);
3078 Input_Set(KEY_LWIN, key & 0x000008);
3079 Input_Set(KEY_RWIN, key & 0x000010);
3080 Input_Set(KEY_LALT, key & 0x000020);
3081 Input_Set(KEY_RALT, key & 0x000040);
3082 Input_Set(KEY_RCTRL, key & 0x002000);
3083 Input_Set(KEY_CAPSLOCK, key & 0x010000);
3084 break;
3085
3086 case 22: /* NSScrollWheel */
3087 dy = Send_CGFloat(ev, selDeltaY);
3088 /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=220175 */
3089 /* delta is in 'line height' units, but I don't know how to map that to actual units. */
3090 /* All I know is that scrolling by '1 wheel notch' produces a delta of around 0.1, and that */
3091 /* sometimes I'll see it go all the way up to 5-6 with a larger wheel scroll. */
3092 /* So mulitplying by 10 doesn't really seem a good idea, instead I just round outwards. */
3093 /* TODO: Figure out if there's a better way than this. */
3094 steps = dy > 0.0f ? Math_Ceil(dy) : Math_Floor(dy);
3095 Mouse_ScrollWheel(steps);
3096 break;
3097
3098 case 5: /* NSMouseMoved */
3099 case 6: /* NSLeftMouseDragged */
3100 case 7: /* NSRightMouseDragged */
3101 case 27: /* NSOtherMouseDragged */
3102 if (GetMouseCoords(&x, &y)) Pointer_SetPosition(0, x, y);
3103
3104 if (Input_RawMode) {
3105 dx = Send_CGFloat(ev, selDeltaX);
3106 dy = Send_CGFloat(ev, selDeltaY);
3107 Event_RaiseRawMove(&PointerEvents.RawMoved, dx, dy);
3108 }
3109 break;
3110 }
3111 objc_msgSend(appHandle, selSendEvent, ev);
3112 }
3113}
3114
3115static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
3116static void ShowDialogCore(const char* title, const char* msg) {
3117 CFStringRef titleCF, msgCF;
3118 id alert;
3119
3120 alert = objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("alloc"));
3121 alert = objc_msgSend(alert, sel_registerName("init"));
3122 titleCF = CFStringCreateWithCString(NULL((void*)0), title, kCFStringEncodingASCII);
3123 msgCF = CFStringCreateWithCString(NULL((void*)0), msg, kCFStringEncodingASCII);
3124
3125 objc_msgSend(alert, sel_registerName("setMessageText:"), titleCF);
3126 objc_msgSend(alert, sel_registerName("setInformativeText:"), msgCF);
3127 objc_msgSend(alert, sel_registerName("addButtonWithTitle:"), CFSTR("OK"));
3128
3129 objc_msgSend(alert, sel_registerName("runModal"));
3130 CFRelease(titleCF);
3131 CFRelease(msgCF);
3132}
3133
3134static struct Bitmap fb_bmp;
3135void Window_AllocFramebuffer(struct Bitmap* bmp) {
3136 bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
3137 fb_bmp = *bmp;
3138}
3139
3140static void View_DrawRect(id self, SEL cmd, CGRect r_) {
3141 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
3142 CGContextRef context = NULL((void*)0);
3143 CGDataProviderRef provider;
3144 CGImageRef image;
3145 CGRect rect;
3146 id nsContext;
3147
3148 /* Unfortunately CGImageRef is immutable, so changing the */
3149 /* underlying data doesn't change what shows when drawing. */
3150 /* TODO: Find a better way of doing this in cocoa.. */
3151 if (!fb_bmp.scan0) return;
3152 nsContext = objc_msgSend((id)objc_getClass("NSGraphicsContext"), selCurrentContext);
3153 context = objc_msgSend(nsContext, selGraphicsPort);
3154
3155 /* TODO: Only update changed bit.. */
3156 rect.origin.x = 0; rect.origin.y = 0;
3157 rect.size.width = WindowInfo.Width;
3158 rect.size.height = WindowInfo.Height;
3159
3160 /* TODO: REPLACE THIS AWFUL HACK */
3161 provider = CGDataProviderCreateWithData(NULL((void*)0), fb_bmp.scan0,
3162 Bitmap_DataSize(fb_bmp.width, fb_bmp.height)((cc_uint32)(fb_bmp.width) * (cc_uint32)(fb_bmp.height) * 4), NULL((void*)0));
3163 image = CGImageCreate(fb_bmp.width, fb_bmp.height, 8, 32, fb_bmp.width * 4, colorSpace,
3164 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, provider, NULL((void*)0), 0, 0);
3165
3166 CGContextDrawImage(context, rect, image);
3167 CGContextSynchronize(context);
3168
3169 CGImageRelease(image);
3170 CGDataProviderRelease(provider);
3171 CGColorSpaceRelease(colorSpace);
3172}
3173
3174void Window_DrawFramebuffer(Rect2D r) {
3175 CGRect rect;
3176 rect.origin.x = r.X;
3177 rect.origin.y = WindowInfo.Height - r.Y - r.Height;
3178 rect.size.width = r.Width;
3179 rect.size.height = r.Height;
3180
3181 objc_msgSend(viewHandle, selSetNeedsDisplay, rect);
3182 objc_msgSend(viewHandle, selDisplayIfNeeded);
3183}
3184
3185void Window_FreeFramebuffer(struct Bitmap* bmp) {
3186 Mem_Free(bmp->scan0);
3187}
3188#endif
3189
3190
3191/*########################################################################################################################*
3192*------------------------------------------------Emscripten canvas window-------------------------------------------------*
3193*#########################################################################################################################*/
3194#elif defined CC_BUILD_WEB
3195#include <emscripten/emscripten.h>
3196#include <emscripten/html5.h>
3197#include <emscripten/key_codes.h>
3198static cc_bool keyboardOpen, needResize;
3199
3200static int RawDpiScale(int x) { return (int)(x * emscripten_get_device_pixel_ratio()); }
3201static int GetCanvasWidth(void) { return EM_ASM_INT_V({ return Module['canvas'].width }); }
3202static int GetCanvasHeight(void) { return EM_ASM_INT_V({ return Module['canvas'].height }); }
3203static int GetScreenWidth(void) { return RawDpiScale(EM_ASM_INT_V({ return screen.width; })); }
3204static int GetScreenHeight(void) { return RawDpiScale(EM_ASM_INT_V({ return screen.height; })); }
3205
3206static void UpdateWindowBounds(void) {
3207 int width = GetCanvasWidth();
3208 int height = GetCanvasHeight();
3209 if (width == WindowInfo.Width && height == WindowInfo.Height) return;
3210
3211 WindowInfo.Width = width;
3212 WindowInfo.Height = height;
3213 Event_RaiseVoid(&WindowEvents.Resized);
3214}
3215
3216static void SetFullscreenBounds(void) {
3217 int width = GetScreenWidth();
3218 int height = GetScreenHeight();
3219 emscripten_set_canvas_element_size("#canvas", width, height);
3220}
3221
3222/* Browser only allows pointer lock requests in response to user input */
3223static void DeferredEnableRawMouse(void) {
3224 EmscriptenPointerlockChangeEvent status;
3225 if (!Input_RawMode) return;
3226
3227 status.isActive = false0;
3228 emscripten_get_pointerlock_status(&status);
3229 if (!status.isActive) emscripten_request_pointerlock("#canvas", false0);
3230}
3231
3232static EM_BOOL OnMouseWheel(int type, const EmscriptenWheelEvent* ev, void* data) {
3233 /* TODO: The scale factor isn't standardised.. is there a better way though? */
3234 Mouse_ScrollWheel(-Math_Sign(ev->deltaY));
3235 DeferredEnableRawMouse();
3236 return true1;
3237}
3238
3239static EM_BOOL OnMouseButton(int type, const EmscriptenMouseEvent* ev, void* data) {
3240 cc_bool down = type == EMSCRIPTEN_EVENT_MOUSEDOWN;
3241 switch (ev->button) {
3242 case 0: Input_Set(KEY_LMOUSE, down); break;
3243 case 1: Input_Set(KEY_MMOUSE, down); break;
3244 case 2: Input_Set(KEY_RMOUSE, down); break;
3245 }
3246
3247 DeferredEnableRawMouse();
3248 return true1;
3249}
3250
3251/* input coordinates are CSS pixels, remap to internal pixels */
3252static void RescaleXY(int* x, int* y) {
3253 double css_width, css_height;
3254 emscripten_get_element_css_size("#canvas", &css_width, &css_height);
3255
3256 if (css_width && css_height) {
3257 *x = (int)(*x * WindowInfo.Width / css_width );
3258 *y = (int)(*y * WindowInfo.Height / css_height);
3259 } else {
3260 /* If css width or height is 0, something is bogus */
3261 /* Better to avoid divsision by 0 in that case though */
3262 }
3263}
3264
3265static EM_BOOL OnMouseMove(int type, const EmscriptenMouseEvent* ev, void* data) {
3266 int x, y, buttons = ev->buttons;
3267 /* Set before position change, in case mouse buttons changed when outside window */
3268 Input_SetNonRepeatable(KEY_LMOUSE, buttons & 0x01);
3269 Input_SetNonRepeatable(KEY_RMOUSE, buttons & 0x02);
3270 Input_SetNonRepeatable(KEY_MMOUSE, buttons & 0x04);
3271
3272 x = ev->targetX; y = ev->targetY;
3273 RescaleXY(&x, &y);
3274 Pointer_SetPosition(0, x, y);
3275 if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, ev->movementX, ev->movementY);
3276 return true1;
3277}
3278
3279/* TODO: Also query mouse coordinates globally and reuse adjustXY here */
3280/* Adjust from document coordinates to element coordinates */
3281static void AdjustXY(int* x, int* y) {
3282 EM_ASM_({
3283 var canvasRect = Module['canvas'].getBoundingClientRect();
3284 HEAP32[$0 >> 2] = HEAP32[$0 >> 2] - canvasRect.left;
3285 HEAP32[$1 >> 2] = HEAP32[$1 >> 2] - canvasRect.top;
3286 }, x, y);
3287}
3288
3289static EM_BOOL OnTouchStart(int type, const EmscriptenTouchEvent* ev, void* data) {
3290 const EmscriptenTouchPoint* t;
3291 int i, x, y;
3292 for (i = 0; i < ev->numTouches; ++i) {
3293 t = &ev->touches[i];
3294 if (!t->isChanged) continue;
3295 x = t->targetX; y = t->targetY;
3296
3297 AdjustXY( &x, &y);
3298 RescaleXY(&x, &y);
3299 Input_AddTouch(t->identifier, x, y);
3300 }
3301 /* Don't intercept touchstart events while keyboard is open, that way */
3302 /* user can still touch to move the caret position in input textbox. */
3303 return !keyboardOpen;
3304}
3305
3306static EM_BOOL OnTouchMove(int type, const EmscriptenTouchEvent* ev, void* data) {
3307 const EmscriptenTouchPoint* t;
3308 int i, x, y;
3309 for (i = 0; i < ev->numTouches; ++i) {
3310 t = &ev->touches[i];
3311 if (!t->isChanged) continue;
3312 x = t->targetX; y = t->targetY;
3313
3314 AdjustXY( &x, &y);
3315 RescaleXY(&x, &y);
3316 Input_UpdateTouch(t->identifier, x, y);
3317 }
3318 /* Don't intercept touchmove events while keyboard is open, that way */
3319 /* user can still touch to move the caret position in input textbox. */
3320 return !keyboardOpen;
3321}
3322
3323static EM_BOOL OnTouchEnd(int type, const EmscriptenTouchEvent* ev, void* data) {
3324 const EmscriptenTouchPoint* t;
3325 int i, x, y;
3326 for (i = 0; i < ev->numTouches; ++i) {
3327 t = &ev->touches[i];
3328 if (!t->isChanged) continue;
3329 x = t->targetX; y = t->targetY;
3330
3331 AdjustXY( &x, &y);
3332 RescaleXY(&x, &y);
3333 Input_RemoveTouch(t->identifier, x, y);
3334 }
3335 /* Don't intercept touchend events while keyboard is open, that way */
3336 /* user can still touch to move the caret position in input textbox. */
3337 return !keyboardOpen;
3338}
3339
3340static EM_BOOL OnFocus(int type, const EmscriptenFocusEvent* ev, void* data) {
3341 WindowInfo.Focused = type == EMSCRIPTEN_EVENT_FOCUS;
3342 Event_RaiseVoid(&WindowEvents.FocusChanged);
3343 return true1;
3344}
3345
3346static EM_BOOL OnResize(int type, const EmscriptenUiEvent* ev, void *data) {
3347 UpdateWindowBounds(); needResize = true1;
3348 return true1;
3349}
3350/* This is only raised when going into fullscreen */
3351static EM_BOOL OnCanvasResize(int type, const void* reserved, void *data) {
3352 UpdateWindowBounds(); needResize = true1;
3353 return false0;
3354}
3355static EM_BOOL OnFullscreenChange(int type, const EmscriptenFullscreenChangeEvent* ev, void *data) {
3356 UpdateWindowBounds(); needResize = true1;
3357 return false0;
3358}
3359
3360static const char* OnBeforeUnload(int type, const void* ev, void *data) {
3361 Window_Close();
3362 return NULL((void*)0);
3363}
3364
3365static int MapNativeKey(int k) {
3366 if (k >= '0' && k <= '9') return k;
3367 if (k >= 'A' && k <= 'Z') return k;
3368 if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
3369 if (k >= DOM_VK_NUMPAD0 && k <= DOM_VK_NUMPAD9) { return KEY_KP0 + (k - DOM_VK_NUMPAD0); }
3370
3371 switch (k) {
3372 case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
3373 case DOM_VK_TAB: return KEY_TAB;
3374 case DOM_VK_RETURN: return KEY_ENTER;
3375 case DOM_VK_SHIFT: return KEY_LSHIFT;
3376 case DOM_VK_CONTROL: return KEY_LCTRL;
3377 case DOM_VK_ALT: return KEY_LALT;
3378 case DOM_VK_PAUSE: return KEY_PAUSE;
3379 case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
3380 case DOM_VK_ESCAPE: return KEY_ESCAPE;
3381 case DOM_VK_SPACE: return KEY_SPACE;
3382
3383 case DOM_VK_PAGE_UP: return KEY_PAGEUP;
3384 case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
3385 case DOM_VK_END: return KEY_END;
3386 case DOM_VK_HOME: return KEY_HOME;
3387 case DOM_VK_LEFT: return KEY_LEFT;
3388 case DOM_VK_UP: return KEY_UP;
3389 case DOM_VK_RIGHT: return KEY_RIGHT;
3390 case DOM_VK_DOWN: return KEY_DOWN;
3391 case DOM_VK_PRINTSCREEN: return KEY_PRINTSCREEN;
3392 case DOM_VK_INSERT: return KEY_INSERT;
3393 case DOM_VK_DELETE: return KEY_DELETE;
3394
3395 case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
3396 case DOM_VK_EQUALS: return KEY_EQUALS;
3397 case DOM_VK_WIN: return KEY_LWIN;
3398 case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
3399 case DOM_VK_ADD: return KEY_KP_PLUS;
3400 case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
3401 case DOM_VK_DECIMAL: return KEY_KP_DECIMAL;
3402 case DOM_VK_DIVIDE: return KEY_KP_DIVIDE;
3403 case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
3404 case DOM_VK_SCROLL_LOCK: return KEY_SCROLLLOCK;
3405
3406 case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
3407 case DOM_VK_COMMA: return KEY_COMMA;
3408 case DOM_VK_PERIOD: return KEY_PERIOD;
3409 case DOM_VK_SLASH: return KEY_SLASH;
3410 case DOM_VK_BACK_QUOTE: return KEY_TILDE;
3411 case DOM_VK_OPEN_BRACKET: return KEY_LBRACKET;
3412 case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
3413 case DOM_VK_CLOSE_BRACKET: return KEY_RBRACKET;
3414 case DOM_VK_QUOTE: return KEY_QUOTE;
3415
3416 /* chrome */
3417 case 186: return KEY_SEMICOLON;
3418 case 187: return KEY_EQUALS;
3419 case 189: return KEY_MINUS;
3420 }
3421 return KEY_NONE;
3422}
3423
3424static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) {
3425 int key = MapNativeKey(ev->keyCode);
3426
3427 if (ev->location == DOM_KEY_LOCATION_RIGHT) {
3428 switch (key) {
3429 case KEY_LALT: key = KEY_RALT; break;
3430 case KEY_LCTRL: key = KEY_RCTRL; break;
3431 case KEY_LSHIFT: key = KEY_RSHIFT; break;
3432 case KEY_LWIN: key = KEY_RWIN; break;
3433 }
3434 }
3435 else if (ev->location == DOM_KEY_LOCATION_NUMPAD) {
3436 switch (key) {
3437 case KEY_ENTER: key = KEY_KP_ENTER; break;
3438 }
3439 }
3440
3441 if (key) Input_Set(key, type == EMSCRIPTEN_EVENT_KEYDOWN);
3442 DeferredEnableRawMouse();
3443
3444 if (!key) return false0;
3445 /* KeyUp always intercepted */
3446 if (type != EMSCRIPTEN_EVENT_KEYDOWN) return true1;
3447
3448 /* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
3449 /* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
3450 if (Key_IsAltPressed()(Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT]) || Key_IsWinPressed()(Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN])) return true1;
3451 if (Key_IsControlPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL]) && key != 'C' && key != 'V') return true1;
3452
3453 /* Space needs special handling, as intercepting this prevents the ' ' key press event */
3454 /* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
3455 if (key == KEY_SPACE) return !keyboardOpen;
3456
3457 /* Must not intercept KeyDown for regular keys, otherwise KeyPress doesn't get raised. */
3458 /* However, do want to prevent browser's behaviour on F11, F5, home etc. */
3459 /* e.g. not preventing F11 means browser makes page fullscreen instead of just canvas */
3460 return (key >= KEY_F1 && key <= KEY_F24) || (key >= KEY_UP && key <= KEY_RIGHT) ||
3461 (key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
3462}
3463
3464static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
3465 char keyChar;
3466 DeferredEnableRawMouse();
3467 /* When on-screen keyboard is open, we don't want to intercept any key presses, */
3468 /* because they should be sent to the HTML text input instead. */
3469 /* (Chrome for android sends keypresses sometimes for '0' to '9' keys) */
3470 /* - If any keys are intercepted, this causes the HTML text input to become */
3471 /* desynchronised from the chat/menu input widget the user sees in game. */
3472 /* - This causes problems such as attempting to backspace all text later to */
3473 /* not actually backspace everything. (because the HTML text input does not */
3474 /* have these intercepted key presses in its text buffer) */
3475 if (Input_TouchMode0 && keyboardOpen) return false0;
3476
3477 if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
3478 Event_RaiseInt(&InputEvents.Press, keyChar);
3479 }
3480 return true1;
3481}
3482
3483/* Really old emscripten versions (e.g. 1.38.21) don't have this defined */
3484/* Can't just use "#window", newer versions switched to const int instead */
3485#ifndef EMSCRIPTEN_EVENT_TARGET_WINDOW
3486#define EMSCRIPTEN_EVENT_TARGET_WINDOW "#window"
3487#endif
3488
3489static void HookEvents(void) {
3490 emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnMouseWheel);
3491 emscripten_set_mousedown_callback("#canvas", NULL((void*)0), 0, OnMouseButton);
3492 emscripten_set_mouseup_callback("#canvas", NULL((void*)0), 0, OnMouseButton);
3493 emscripten_set_mousemove_callback("#canvas", NULL((void*)0), 0, OnMouseMove);
3494 emscripten_set_fullscreenchange_callback("#canvas", NULL((void*)0), 0, OnFullscreenChange);
3495
3496 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnFocus);
3497 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnFocus);
3498 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnResize);
3499 emscripten_set_beforeunload_callback( NULL((void*)0), OnBeforeUnload);
3500
3501 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnKey);
3502 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnKey);
3503 emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnKeyPress);
3504
3505 emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnTouchStart);
3506 emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnTouchMove);
3507 emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnTouchEnd);
3508 emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, OnTouchEnd);
3509}
3510
3511static void UnhookEvents(void) {
3512 emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3513 emscripten_set_mousedown_callback("#canvas", NULL((void*)0), 0, NULL((void*)0));
3514 emscripten_set_mouseup_callback("#canvas", NULL((void*)0), 0, NULL((void*)0));
3515 emscripten_set_mousemove_callback("#canvas", NULL((void*)0), 0, NULL((void*)0));
3516
3517 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3518 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3519 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3520 emscripten_set_beforeunload_callback( NULL((void*)0), NULL((void*)0));
3521
3522 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3523 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3524 emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3525
3526 emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3527 emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3528 emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3529 emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL((void*)0), 0, NULL((void*)0));
3530}
3531
3532void Window_Init(void) {
3533 DisplayInfo.Width = GetScreenWidth();
3534 DisplayInfo.Height = GetScreenHeight();
3535 DisplayInfo.Depth = 24;
3536
3537 DisplayInfo.ScaleX = emscripten_get_device_pixel_ratio();
3538 DisplayInfo.ScaleY = DisplayInfo.ScaleX;
3539
3540 /* Copy text, but only if user isn't selecting something else on the webpage */
3541 /* (don't check window.clipboardData here, that's handled in Clipboard_SetText instead) */
3542 EM_ASM(window.addEventListener('copy',
3543 function(e) {
3544 if (window.getSelection && window.getSelection().toString()) return;
3545 if (window.cc_copyText) {
3546 if (e.clipboardData) { e.clipboardData.setData('text/plain', window.cc_copyText); }
3547 e.preventDefault();
3548 window.cc_copyText = null;
3549 }
3550 });
3551 );
3552
3553 /* Paste text (window.clipboardData is handled in Clipboard_RequestText instead) */
3554 EM_ASM(window.addEventListener('paste',
3555 function(e) {
3556 var contents = e.clipboardData ? e.clipboardData.getData('text/plain') : "";
3557 ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
3558 });
3559 );
3560
3561 /* iOS 13 on iPad doesn't identify itself as iPad by default anymore */
3562 /* https://stackoverflow.com/questions/57765958/how-to-detect-ipad-and-ipad-os-version-in-ios-13-and-up */
3563 Input_TouchMode0 = EM_ASM_INT_V({
3564 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
3565 (navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);
3566 });
3567 Pointers_Count1 = Input_TouchMode0 ? 0 : 1;
3568
3569 /* iOS shifts the whole webpage up when opening chat, which causes problems */
3570 /* as the chat/send butons are positioned at the top of the canvas - they */
3571 /* get pushed offscreen and can't be used at all anymore. So handle this */
3572 /* case specially by positioning them at the bottom instead for iOS. */
3573 WindowInfo.SoftKeyboard = EM_ASM_INT_V({ return /iPhone|iPad|iPod/i.test(navigator.userAgent); })
3574 ? SOFT_KEYBOARD_SHIFT : SOFT_KEYBOARD_RESIZE;
3575}
3576
3577void Window_Create(int width, int height) {
3578 WindowInfo.Exists = true1;
3579 WindowInfo.Focused = true1;
3580 HookEvents();
3581 /* Let the webpage decide on initial bounds */
3582 WindowInfo.Width = GetCanvasWidth();
3583 WindowInfo.Height = GetCanvasHeight();
3584
3585 /* Create wrapper div if necessary */
3586 EM_ASM({
3587 var agent = navigator.userAgent;
3588 var canvas = Module['canvas'];
3589 window.cc_container = document.body;
3590
3591 if (/Android/i.test(agent) && /Chrome/i.test(agent)) {
3592 var wrapper = document.createElement("div");
3593 wrapper.id = 'canvas_wrapper';
3594
3595 canvas.parentNode.insertBefore(wrapper, canvas);
3596 wrapper.appendChild(canvas);
3597 window.cc_container = wrapper;
3598 }
3599 });
3600}
3601
3602void Window_SetTitle(const cc_string* title) {
3603 char str[NATIVE_STR_LEN600];
3604 Platform_EncodeUtf8(str, title);
3605 EM_ASM_({ document.title = UTF8ToString($0); }, str);
3606}
3607
3608static RequestClipboardCallback clipboard_func;
3609static void* clipboard_obj;
3610
3611EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
3612 cc_string str; char strBuffer[512];
3613 if (!clipboard_func) return;
3614
3615 String_InitArray(str, strBuffer)str.buffer = strBuffer; str.length = 0; str.capacity = sizeof
(strBuffer);
;
3616 String_AppendUtf8(&str, src, String_CalcLen(src, 2048));
3617 clipboard_func(&str, clipboard_obj);
3618 clipboard_func = NULL((void*)0);
3619}
3620
3621void Clipboard_GetText(cc_string* value) { }
3622void Clipboard_SetText(const cc_string* value) {
3623 char str[NATIVE_STR_LEN600];
3624 Platform_EncodeUtf8(str, value);
3625
3626 /* For IE11, use window.clipboardData to set the clipboard */
3627 /* For other browsers, instead use the window.copy events */
3628 EM_ASM_({
3629 if (window.clipboardData) {
3630 if (window.getSelection && window.getSelection().toString()) return;
3631 window.clipboardData.setData('Text', UTF8ToString($0));
3632 } else {
3633 window.cc_copyText = UTF8ToString($0);
3634 }
3635 }, str);
3636}
3637
3638void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
3639 clipboard_func = callback;
3640 clipboard_obj = obj;
3641
3642 /* For IE11, use window.clipboardData to get the clipboard */
3643 EM_ASM_({
3644 if (!window.clipboardData) return;
3645 var contents = window.clipboardData.getData('Text');
3646 ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
3647 });
3648}
3649
3650void Window_Show(void) { }
3651
3652int Window_GetWindowState(void) {
3653 EmscriptenFullscreenChangeEvent status = { 0 };
3654 emscripten_get_fullscreen_status(&status);
3655 return status.isFullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL;
3656}
3657
3658cc_result Window_EnterFullscreen(void) {
3659 EmscriptenFullscreenStrategy strategy;
3660 const char* target;
3661 int res;
3662 strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
3663 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
3664 strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
3665
3666 strategy.canvasResizedCallback = OnCanvasResize;
3667 strategy.canvasResizedCallbackUserData = NULL((void*)0);
3668
3669 /* For chrome on android, need to make container div fullscreen instead */
3670 res = EM_ASM_INT_V({ return document.getElementById('canvas_wrapper') ? 1 : 0; });
3671 target = res ? "canvas_wrapper" : "#canvas";
3672
3673 res = emscripten_request_fullscreen_strategy(target, 1, &strategy);
3674 if (res == EMSCRIPTEN_RESULT_NOT_SUPPORTED) res = ERR_NOT_SUPPORTED;
3675 if (res) return res;
3676
3677 /* emscripten sets css size to screen's base width/height, */
3678 /* except that becomes wrong when device rotates. */
3679 /* Better to just set CSS width/height to always be 100% */
3680 EM_ASM({
3681 var canvas = Module['canvas'];
3682 canvas.style.width = '100%';
3683 canvas.style.height = '100%';
3684 });
3685
3686 /* By default, pressing Escape will immediately exit fullscreen - which is */
3687 /* quite annoying given that it is also the Menu key. Some browsers allow */
3688 /* 'locking' the Escape key, so that you have to hold down Escape to exit. */
3689 /* NOTE: This ONLY works when the webpage is a https:// one */
3690 EM_ASM({ try { navigator.keyboard.lock(["Escape"]); } catch (ex) { } });
3691 return 0;
3692}
3693
3694cc_result Window_ExitFullscreen(void) {
3695 emscripten_exit_fullscreen();
3696 UpdateWindowBounds();
3697 return 0;
3698}
3699
3700void Window_SetSize(int width, int height) {
3701 emscripten_set_canvas_element_size("#canvas", width, height);
3702 /* CSS size is in CSS units not pixel units */
3703 emscripten_set_element_css_size("#canvas", width / DisplayInfo.ScaleX, height / DisplayInfo.ScaleY);
3704 UpdateWindowBounds();
3705}
3706
3707void Window_Close(void) {
3708 WindowInfo.Exists = false0;
3709 Event_RaiseVoid(&WindowEvents.Closing);
3710 /* If the game is closed while in fullscreen, the last rendered frame stays */
3711 /* shown in fullscreen, but the game can't be interacted with anymore */
3712 Window_ExitFullscreen();
3713
3714 /* Don't want cursor stuck on the dead 0,0 canvas */
3715 Window_DisableRawMouse();
3716 Window_SetSize(0, 0);
3717 UnhookEvents();
3718}
3719
3720void Window_ProcessEvents(void) {
3721 if (!needResize) return;
3722 needResize = false0;
3723 if (!WindowInfo.Exists) return;
3724
3725 if (Window_GetWindowState() == WINDOW_STATE_FULLSCREEN) {
3726 SetFullscreenBounds();
3727 } else {
3728 EM_ASM( if (typeof(resizeGameCanvas) === 'function') resizeGameCanvas(); );
3729 }
3730 UpdateWindowBounds();
3731}
3732
3733/* Not needed because browser provides relative mouse and touch events */
3734static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
3735/* Not allowed to move cursor from javascript */
3736void Cursor_SetPosition(int x, int y) { }
3737
3738static void Cursor_DoSetVisible(cc_bool visible) {
3739 if (visible) {
3740 EM_ASM(Module['canvas'].style['cursor'] = 'default'; );
3741 } else {
3742 EM_ASM(Module['canvas'].style['cursor'] = 'none'; );
3743 }
3744}
3745
3746static void ShowDialogCore(const char* title, const char* msg) {
3747 EM_ASM_({ alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); }, title, msg);
3748}
3749
3750static OpenFileDialogCallback uploadCallback;
3751EMSCRIPTEN_KEEPALIVE void Window_OnFileUploaded(const char* src) {
3752 cc_string file; char buffer[FILENAME_SIZE260];
3753 String_InitArray(file, buffer)file.buffer = buffer; file.length = 0; file.capacity = sizeof
(buffer);
;
3754
3755 String_AppendUtf8(&file, src, String_Length(src));
3756 uploadCallback(&file);
3757 uploadCallback = NULL((void*)0);
3758}
3759
3760cc_result Window_OpenFileDialog(const char* filter, OpenFileDialogCallback callback) {
3761 uploadCallback = callback;
3762 EM_ASM_({
3763 var elem = window.cc_uploadElem;
3764 if (!elem) {
3765 elem = document.createElement('input');
3766 elem.setAttribute('type', 'file');
3767 elem.setAttribute('style', 'display: none');
3768 elem.accept = UTF8ToString($0);
3769
3770 elem.addEventListener('change',
3771 function(ev) {
3772 var files = ev.target.files;
3773 for (var i = 0; i < files.length; i++) {
3774 var reader = new FileReader();
3775 var name = files[i].name;
3776
3777 reader.onload = function(e) {
3778 var data = new Uint8Array(e.target.result);
3779 FS.createDataFile('/', name, data, true1, true1, true1);
3780 ccall('Window_OnFileUploaded', 'void', ['string'], ['/' + name]);
3781 FS.unlink('/' + name);
3782 };
3783 reader.readAsArrayBuffer(files[i]);
3784 }
3785 window.cc_container.removeChild(window.cc_uploadElem);
3786 window.cc_uploadElem = null;
3787 }, false0);
3788 window.cc_uploadElem = elem;
3789 window.cc_container.appendChild(elem);
3790 }
3791 elem.click();
3792 }, filter);
3793 return ERR_NOT_SUPPORTED;
3794}
3795
3796void Window_AllocFramebuffer(struct Bitmap* bmp) { }
3797void Window_DrawFramebuffer(Rect2D r) { }
3798void Window_FreeFramebuffer(struct Bitmap* bmp) { }
3799
3800EMSCRIPTEN_KEEPALIVE void Window_OnTextChanged(const char* src) {
3801 cc_string str; char buffer[800];
3802 String_InitArray(str, buffer)str.buffer = buffer; str.length = 0; str.capacity = sizeof(buffer
);
;
3803
3804 String_AppendUtf8(&str, src, String_CalcLen(src, 3200));
3805 Event_RaiseString(&InputEvents.TextChanged, &str);
3806}
3807
3808void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
3809 char str[NATIVE_STR_LEN600];
3810 keyboardOpen = true1;
3811 if (!Input_TouchMode0) return;
3812 Platform_EncodeUtf8(str, args->text);
3813 Platform_LogConst("OPEN SESAME");
3814
3815 EM_ASM_({
3816 var elem = window.cc_inputElem;
3817 if (!elem) {
3818 if ($1 == 1) {
3819 elem = document.createElement('input');
3820 elem.setAttribute('inputmode', 'decimal');
3821 } else {
3822 elem = document.createElement('textarea');
3823 }
3824 elem.setAttribute('style', 'position:absolute; left:0; bottom:0; margin: 0px; width: 100%');
3825 elem.value = UTF8ToString($0);
3826
3827 elem.addEventListener('input',
3828 function(ev) {
3829 ccall('Window_OnTextChanged', 'void', ['string'], [ev.target.value]);
3830 }, false0);
3831 window.cc_inputElem = elem;
3832
3833 window.cc_divElem = document.createElement('div');
3834 window.cc_divElem.setAttribute('style', 'position:absolute; left:0; top:0; width:100%; height:100%; background-color: black; opacity:0.4; resize:none; pointer-events:none;');
3835
3836 window.cc_container.appendChild(window.cc_divElem);
3837 window.cc_container.appendChild(elem);
3838 }
3839 elem.focus();
3840 elem.click();
3841 }, str, args->type);
3842}
3843
3844void Window_SetKeyboardText(const cc_string* text) {
3845 char str[NATIVE_STR_LEN600];
3846 if (!Input_TouchMode0) return;
3847 Platform_EncodeUtf8(str, text);
3848
3849 EM_ASM_({
3850 if (!window.cc_inputElem) return;
3851 var str = UTF8ToString($0);
3852
3853 if (str == window.cc_inputElem.value) return;
3854 window.cc_inputElem.value = str;
3855 }, str);
3856}
3857
3858void Window_CloseKeyboard(void) {
3859 keyboardOpen = false0;
3860 if (!Input_TouchMode0) return;
3861
3862 EM_ASM({
3863 if (!window.cc_inputElem) return;
3864 window.cc_container.removeChild(window.cc_divElem);
3865 window.cc_container.removeChild(window.cc_inputElem);
3866 window.cc_divElem = null;
3867 window.cc_inputElem = null;
3868 });
3869}
3870
3871void Window_EnableRawMouse(void) {
3872 RegrabMouse();
3873 /* defer pointerlock request until next user input */
3874 Input_RawMode = true1;
3875}
3876void Window_UpdateRawMouse(void) { }
3877
3878void Window_DisableRawMouse(void) {
3879 RegrabMouse();
3880 emscripten_exit_pointerlock();
3881 Input_RawMode = false0;
3882}
3883
3884
3885/*########################################################################################################################*
3886*------------------------------------------------Android activity window-------------------------------------------------*
3887*#########################################################################################################################*/
3888#elif defined CC_BUILD_ANDROID
3889#include <android/native_window.h>
3890#include <android/native_window_jni.h>
3891#include <android/keycodes.h>
3892static ANativeWindow* win_handle;
3893
3894static void RefreshWindowBounds(void) {
3895 WindowInfo.Width = ANativeWindow_getWidth(win_handle);
3896 WindowInfo.Height = ANativeWindow_getHeight(win_handle);
3897 Platform_Log2("SCREEN BOUNDS: %i,%i", &WindowInfo.Width, &WindowInfo.Height);
3898 Event_RaiseVoid(&WindowEvents.Resized);
3899}
3900
3901static int MapNativeKey(int code) {
3902 if (code >= AKEYCODE_0 && code <= AKEYCODE_9) return (code - AKEYCODE_0) + '0';
3903 if (code >= AKEYCODE_A && code <= AKEYCODE_Z) return (code - AKEYCODE_A) + 'A';
3904 if (code >= AKEYCODE_F1 && code <= AKEYCODE_F12) return (code - AKEYCODE_F1) + KEY_F1;
3905 if (code >= AKEYCODE_NUMPAD_0 && code <= AKEYCODE_NUMPAD_9) return (code - AKEYCODE_NUMPAD_0) + KEY_KP0;
3906
3907 switch (code) {
3908 /* TODO: AKEYCODE_STAR */
3909 /* TODO: AKEYCODE_POUND */
3910 case AKEYCODE_BACK: return KEY_ESCAPE;
3911 case AKEYCODE_COMMA: return KEY_COMMA;
3912 case AKEYCODE_PERIOD: return KEY_PERIOD;
3913 case AKEYCODE_ALT_LEFT: return KEY_LALT;
3914 case AKEYCODE_ALT_RIGHT: return KEY_RALT;
3915 case AKEYCODE_SHIFT_LEFT: return KEY_LSHIFT;
3916 case AKEYCODE_SHIFT_RIGHT: return KEY_RSHIFT;
3917 case AKEYCODE_TAB: return KEY_TAB;
3918 case AKEYCODE_SPACE: return KEY_SPACE;
3919 case AKEYCODE_ENTER: return KEY_ENTER;
3920 case AKEYCODE_DEL: return KEY_BACKSPACE;
3921 case AKEYCODE_GRAVE: return KEY_TILDE;
3922 case AKEYCODE_MINUS: return KEY_MINUS;
3923 case AKEYCODE_EQUALS: return KEY_EQUALS;
3924 case AKEYCODE_LEFT_BRACKET: return KEY_LBRACKET;
3925 case AKEYCODE_RIGHT_BRACKET: return KEY_RBRACKET;
3926 case AKEYCODE_BACKSLASH: return KEY_BACKSLASH;
3927 case AKEYCODE_SEMICOLON: return KEY_SEMICOLON;
3928 case AKEYCODE_APOSTROPHE: return KEY_QUOTE;
3929 case AKEYCODE_SLASH: return KEY_SLASH;
3930 /* TODO: AKEYCODE_AT */
3931 /* TODO: AKEYCODE_PLUS */
3932 /* TODO: AKEYCODE_MENU */
3933 case AKEYCODE_PAGE_UP: return KEY_PAGEUP;
3934 case AKEYCODE_PAGE_DOWN: return KEY_PAGEDOWN;
3935 case AKEYCODE_ESCAPE: return KEY_ESCAPE;
3936 case AKEYCODE_FORWARD_DEL: return KEY_DELETE;
3937 case AKEYCODE_CTRL_LEFT: return KEY_LCTRL;
3938 case AKEYCODE_CTRL_RIGHT: return KEY_RCTRL;
3939 case AKEYCODE_CAPS_LOCK: return KEY_CAPSLOCK;
3940 case AKEYCODE_SCROLL_LOCK: return KEY_SCROLLLOCK;
3941 case AKEYCODE_META_LEFT: return KEY_LWIN;
3942 case AKEYCODE_META_RIGHT: return KEY_RWIN;
3943 case AKEYCODE_SYSRQ: return KEY_PRINTSCREEN;
3944 case AKEYCODE_BREAK: return KEY_PAUSE;
3945 case AKEYCODE_INSERT: return KEY_INSERT;
3946 case AKEYCODE_NUM_LOCK: return KEY_NUMLOCK;
3947 case AKEYCODE_NUMPAD_DIVIDE: return KEY_KP_DIVIDE;
3948 case AKEYCODE_NUMPAD_MULTIPLY: return KEY_KP_MULTIPLY;
3949 case AKEYCODE_NUMPAD_SUBTRACT: return KEY_KP_MINUS;
3950 case AKEYCODE_NUMPAD_ADD: return KEY_KP_PLUS;
3951 case AKEYCODE_NUMPAD_DOT: return KEY_KP_DECIMAL;
3952 case AKEYCODE_NUMPAD_ENTER: return KEY_KP_ENTER;
3953 }
3954 return KEY_NONE;
3955}
3956
3957static void JNICALL java_processKeyDown(JNIEnv* env, jobject o, jint code) {
3958 int key = MapNativeKey(code);
3959 Platform_Log2("KEY - DOWN %i,%i", &code, &key);
3960 if (key) Input_SetPressed(key);
3961}
3962
3963static void JNICALL java_processKeyUp(JNIEnv* env, jobject o, jint code) {
3964 int key = MapNativeKey(code);
3965 Platform_Log2("KEY - UP %i,%i", &code, &key);
3966 if (key) Input_SetReleased(key);
3967}
3968
3969static void JNICALL java_processKeyChar(JNIEnv* env, jobject o, jint code) {
3970 char keyChar;
3971 int key = MapNativeKey(code);
3972 Platform_Log2("KEY - PRESS %i,%i", &code, &key);
3973
3974 if (Convert_TryCodepointToCP437(code, &keyChar)) {
3975 Event_RaiseInt(&InputEvents.Press, keyChar);
3976 }
3977}
3978
3979static void JNICALL java_processKeyText(JNIEnv* env, jobject o, jstring str) {
3980 char buffer[NATIVE_STR_LEN600];
3981 cc_string text = JavaGetString(env, str, buffer);
3982 Platform_Log1("KEY - TEXT %s", &text);
3983 Event_RaiseString(&InputEvents.TextChanged, &text);
3984}
3985
3986static void JNICALL java_processMouseDown(JNIEnv* env, jobject o, jint id, jint x, jint y) {
3987 Platform_Log3("MOUSE %i - DOWN %i,%i", &id, &x, &y);
3988 Input_AddTouch(id, x, y);
3989}
3990
3991static void JNICALL java_processMouseUp(JNIEnv* env, jobject o, jint id, jint x, jint y) {
3992 Platform_Log3("MOUSE %i - UP %i,%i", &id, &x, &y);
3993 Input_RemoveTouch(id, x, y);
3994}
3995
3996static void JNICALL java_processMouseMove(JNIEnv* env, jobject o, jint id, jint x, jint y) {
3997 Platform_Log3("MOUSE %i - MOVE %i,%i", &id, &x, &y);
3998 Input_UpdateTouch(id, x, y);
3999}
4000
4001static void JNICALL java_processSurfaceCreated(JNIEnv* env, jobject o, jobject surface) {
4002 Platform_LogConst("WIN - CREATED");
4003 win_handle = ANativeWindow_fromSurface(env, surface);
4004 WindowInfo.Handle = win_handle;
4005 RefreshWindowBounds();
4006 /* TODO: Restore context */
4007 Event_RaiseVoid(&WindowEvents.Created);
4008}
4009
4010#include "Graphics.h"
4011static void JNICALL java_processSurfaceDestroyed(JNIEnv* env, jobject o) {
4012 Platform_LogConst("WIN - DESTROYED");
4013 if (win_handle) ANativeWindow_release(win_handle);
4014
4015 win_handle = NULL((void*)0);
4016 WindowInfo.Handle = NULL((void*)0);
4017 /* eglSwapBuffers might return EGL_BAD_SURFACE, EGL_BAD_ALLOC, or some other error */
4018 /* Instead the context is lost here in a consistent manner */
4019 if (Gfx.Created) Gfx_LoseContext("surface lost");
4020 JavaCallVoid(env, "processedSurfaceDestroyed", "()V", NULL((void*)0));
4021}
4022
4023static void JNICALL java_processSurfaceResized(JNIEnv* env, jobject o, jobject surface) {
4024 Platform_LogConst("WIN - RESIZED");
4025 RefreshWindowBounds();
4026}
4027
4028static void JNICALL java_processSurfaceRedrawNeeded(JNIEnv* env, jobject o) {
4029 Platform_LogConst("WIN - REDRAW");
4030 Event_RaiseVoid(&WindowEvents.Redraw);
4031}
4032
4033static void JNICALL java_onStart(JNIEnv* env, jobject o) {
4034 Platform_LogConst("APP - ON START");
4035}
4036
4037static void JNICALL java_onStop(JNIEnv* env, jobject o) {
4038 Platform_LogConst("APP - ON STOP");
4039}
4040
4041static void JNICALL java_onResume(JNIEnv* env, jobject o) {
4042 Platform_LogConst("APP - ON RESUME");
4043 /* TODO: Resume rendering */
4044}
4045
4046static void JNICALL java_onPause(JNIEnv* env, jobject o) {
4047 Platform_LogConst("APP - ON PAUSE");
4048 /* TODO: Disable rendering */
4049}
4050
4051static void JNICALL java_onDestroy(JNIEnv* env, jobject o) {
4052 Platform_LogConst("APP - ON DESTROY");
4053
4054 if (WindowInfo.Exists) Window_Close();
4055 /* TODO: signal to java code we're done */
4056 JavaCallVoid(env, "processedDestroyed", "()V", NULL((void*)0));
4057}
4058
4059static void JNICALL java_onGotFocus(JNIEnv* env, jobject o) {
4060 Platform_LogConst("APP - GOT FOCUS");
4061 WindowInfo.Focused = true1;
4062 Event_RaiseVoid(&WindowEvents.FocusChanged);
4063}
4064
4065static void JNICALL java_onLostFocus(JNIEnv* env, jobject o) {
4066 Platform_LogConst("APP - LOST FOCUS");
4067 WindowInfo.Focused = false0;
4068 Event_RaiseVoid(&WindowEvents.FocusChanged);
4069 /* TODO: Disable rendering? */
4070}
4071
4072static void JNICALL java_onLowMemory(JNIEnv* env, jobject o) {
4073 Platform_LogConst("APP - LOW MEM");
4074 /* TODO: Low memory */
4075}
4076
4077static const JNINativeMethod methods[19] = {
4078 { "processKeyDown", "(I)V", java_processKeyDown },
4079 { "processKeyUp", "(I)V", java_processKeyUp },
4080 { "processKeyChar", "(I)V", java_processKeyChar },
4081 { "processKeyText", "(Ljava/lang/String;)V", java_processKeyText },
4082
4083 { "processMouseDown", "(III)V", java_processMouseDown },
4084 { "processMouseUp", "(III)V", java_processMouseUp },
4085 { "processMouseMove", "(III)V", java_processMouseMove },
4086
4087 { "processSurfaceCreated", "(Landroid/view/Surface;)V", java_processSurfaceCreated },
4088 { "processSurfaceDestroyed", "()V", java_processSurfaceDestroyed },
4089 { "processSurfaceResized", "(Landroid/view/Surface;)V", java_processSurfaceResized },
4090 { "processSurfaceRedrawNeeded", "()V", java_processSurfaceRedrawNeeded },
4091
4092 { "processOnStart", "()V", java_onStart },
4093 { "processOnStop", "()V", java_onStop },
4094 { "processOnResume", "()V", java_onResume },
4095 { "processOnPause", "()V", java_onPause },
4096 { "processOnDestroy", "()V", java_onDestroy },
4097
4098 { "processOnGotFocus", "()V", java_onGotFocus },
4099 { "processOnLostFocus", "()V", java_onLostFocus },
4100 { "processOnLowMemory", "()V", java_onLowMemory }
4101};
4102
4103void Window_Init(void) {
4104 JNIEnv* env;
4105 /* TODO: ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN, 0); */
4106 JavaGetCurrentEnv(env);
4107 JavaRegisterNatives(env, methods);
4108
4109 WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
4110 Input_TouchMode0 = true1;
4111 DisplayInfo.Depth = 32;
4112 DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL((void*)0));
4113 DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL((void*)0));
4114}
4115
4116void Window_Create(int width, int height) {
4117 WindowInfo.Exists = true1;
4118 /* actual window creation is done when processSurfaceCreated is received */
4119}
4120
4121static cc_bool winCreated;
4122static void OnWindowCreated(void* obj) { winCreated = true1; }
4123cc_bool Window_RemakeSurface(void) {
4124 JNIEnv* env;
4125 JavaGetCurrentEnv(env);
4126 winCreated = false0;
4127
4128 /* Force window to be destroyed and re-created */
4129 /* (see comments in setupForGame for why this has to be done) */
4130 JavaCallVoid(env, "setupForGame", "()V", NULL((void*)0));
4131 Event_Register_(&WindowEvents.Created, NULL, OnWindowCreated)Event_Register((struct Event_Void*)(&WindowEvents.Created
), ((void*)0), (Event_Void_Callback)(OnWindowCreated))
;
4132 Platform_LogConst("Entering wait for window exist loop..");
4133
4134 /* Loop until window gets created async */
4135 while (WindowInfo.Exists && !winCreated) {
4136 Window_ProcessEvents();
4137 Thread_Sleep(10);
4138 }
4139
4140 Platform_LogConst("OK window created..");
4141 Event_Unregister_(&WindowEvents.Created, NULL, OnWindowCreated)Event_Unregister((struct Event_Void*)(&WindowEvents.Created
), ((void*)0), (Event_Void_Callback)(OnWindowCreated))
;
4142 return winCreated;
4143}
4144
4145void Window_SetTitle(const cc_string* title) {
4146 /* TODO: Implement this somehow */
4147 /* Maybe https://stackoverflow.com/questions/2198410/how-to-change-title-of-activity-in-android */
4148}
4149
4150void Clipboard_GetText(cc_string* value) {
4151 JavaCall_Void_String("getClipboardText", value);
4152}
4153void Clipboard_SetText(const cc_string* value) {
4154 JavaCall_String_Void("setClipboardText", value);
4155}
4156
4157void Window_Show(void) { } /* Window already visible */
4158int Window_GetWindowState(void) {
4159 JNIEnv* env;
4160 JavaGetCurrentEnv(env);
4161 return JavaCallInt(env, "getWindowState", "()I", NULL((void*)0));
4162}
4163
4164cc_result Window_EnterFullscreen(void) {
4165 JNIEnv* env;
4166 JavaGetCurrentEnv(env);
4167 JavaCallVoid(env, "enterFullscreen", "()V", NULL((void*)0));
4168 return 0;
4169}
4170
4171cc_result Window_ExitFullscreen(void) {
4172 JNIEnv* env;
4173 JavaGetCurrentEnv(env);
4174 JavaCallVoid(env, "exitFullscreen", "()V", NULL((void*)0));
4175 return 0;
4176}
4177
4178void Window_SetSize(int width, int height) { }
4179
4180void Window_Close(void) {
4181 WindowInfo.Exists = false0;
4182 Event_RaiseVoid(&WindowEvents.Closing);
4183 /* TODO: Do we need to call finish here */
4184 /* ANativeActivity_finish(app->activity); */
4185}
4186
4187void Window_ProcessEvents(void) {
4188 JNIEnv* env;
4189 JavaGetCurrentEnv(env);
4190 /* TODO: Cache the java env and cache the method ID!!!!! */
4191 JavaCallVoid(env, "processEvents", "()V", NULL((void*)0));
4192}
4193
4194/* No actual mouse cursor */
4195static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
4196void Cursor_SetPosition(int x, int y) { }
4197static void Cursor_DoSetVisible(cc_bool visible) { }
4198
4199static void ShowDialogCore(const char* title, const char* msg) {
4200 JNIEnv* env;
4201 jvalue args[2];
4202 JavaGetCurrentEnv(env);
4203
4204 Platform_LogConst(title);
4205 Platform_LogConst(msg);
4206
4207 args[0].l = JavaMakeConst(env, title);
4208 args[1].l = JavaMakeConst(env, msg);
4209 JavaCallVoid(env, "showAlert", "(Ljava/lang/String;Ljava/lang/String;)V", args);
4210 (*env)->DeleteLocalRef(env, args[0].l);
4211 (*env)->DeleteLocalRef(env, args[1].l);
4212}
4213
4214static struct Bitmap fb_bmp;
4215void Window_AllocFramebuffer(struct Bitmap* bmp) {
4216 bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
4217 fb_bmp = *bmp;
4218}
4219
4220void Window_DrawFramebuffer(Rect2D r) {
4221 ANativeWindow_Buffer buffer;
4222 cc_uint32* src;
4223 cc_uint32* dst;
4224 ARect b;
4225 int y, res, size;
4226
4227 /* window not created yet */
4228 if (!win_handle) return;
4229 b.left = r.X; b.right = r.X + r.Width;
4230 b.top = r.Y; b.bottom = r.Y + r.Height;
4231
4232 /* Platform_Log4("DIRTY: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
4233 res = ANativeWindow_lock(win_handle, &buffer, &b);
4234 if (res) Logger_Abort2(res, "Locking window pixels");
4235 /* Platform_Log4("ADJUS: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
4236
4237 /* In some rare cases, the returned locked region will be entire area of the surface */
4238 /* This can cause a crash if the surface has been resized (i.e. device rotated), */
4239 /* but the framebuffer has not been resized yet. So always constrain bounds. */
4240 b.left = min(b.left, fb_bmp.width)((b.left) < (fb_bmp.width) ? (b.left) : (fb_bmp.width)); b.right = min(b.right, fb_bmp.width)((b.right) < (fb_bmp.width) ? (b.right) : (fb_bmp.width));
4241 b.top = min(b.top, fb_bmp.height)((b.top) < (fb_bmp.height) ? (b.top) : (fb_bmp.height)); b.bottom = min(b.bottom, fb_bmp.height)((b.bottom) < (fb_bmp.height) ? (b.bottom) : (fb_bmp.height
))
;
4242
4243 src = (cc_uint32*)fb_bmp.scan0 + b.left;
4244 dst = (cc_uint32*)buffer.bits + b.left;
4245 size = (b.right - b.left) * 4;
4246
4247 for (y = b.top; y < b.bottom; y++) {
4248 Mem_Copy(dst + y * buffer.stride, src + y * fb_bmp.width, size);
4249 }
4250 res = ANativeWindow_unlockAndPost(win_handle);
4251 if (res) Logger_Abort2(res, "Unlocking window pixels");
4252}
4253
4254void Window_FreeFramebuffer(struct Bitmap* bmp) {
4255 Mem_Free(bmp->scan0);
4256}
4257
4258void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
4259 JNIEnv* env;
4260 jvalue args[2];
4261 JavaGetCurrentEnv(env);
4262
4263 args[0].l = JavaMakeString(env, args->text);
4264 args[1].i = args->type;
4265 JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
4266 (*env)->DeleteLocalRef(env, args[0].l);
4267}
4268
4269void Window_SetKeyboardText(const cc_string* text) {
4270 JNIEnv* env;
4271 jvalue args[1];
4272 JavaGetCurrentEnv(env);
4273
4274 args[0].l = JavaMakeString(env, text);
4275 JavaCallVoid(env, "setKeyboardText", "(Ljava/lang/String;)V", args);
4276 (*env)->DeleteLocalRef(env, args[0].l);
4277}
4278
4279void Window_CloseKeyboard(void) {
4280 JNIEnv* env;
4281 JavaGetCurrentEnv(env);
4282 JavaCallVoid(env, "closeKeyboard", "()V", NULL((void*)0));
4283}
4284
4285void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); }
4286void Window_UpdateRawMouse(void) { }
4287void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
4288#endif
4289
4290
4291
4292#ifdef CC_BUILD_GL
4293/* OpenGL contexts are heavily tied to the window, so for simplicitly are also included here */
4294/* SDL and EGL are platform agnostic, other OpenGL context backends are tied to one windowing system. */
4295#define GLCONTEXT_DEFAULT_DEPTH24 24
4296#define GLContext_IsInvalidAddress(ptr)(ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr
== (void*)2)
(ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr == (void*)2)
4297
4298void GLContext_GetAll(const struct DynamicLibSym* syms, int count) {
4299 int i;
4300 for (i = 0; i < count; i++) {
4301 *syms[i].symAddr = GLContext_GetAddress(syms[i].name);
4302 }
4303}
4304
4305/*########################################################################################################################*
4306*-------------------------------------------------------SDL OpenGL--------------------------------------------------------*
4307*#########################################################################################################################*/
4308#if defined CC_BUILD_SDL
4309static SDL_GLContext win_ctx;
4310
4311void GLContext_Create(void) {
4312 struct GraphicsMode mode;
4313 InitGraphicsMode(&mode);
4314 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, mode.R);
4315 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G);
4316 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, mode.B);
4317 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A);
4318
4319 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH24);
4320 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
4321 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true1);
4322
4323 win_ctx = SDL_GL_CreateContext(win_handle);
4324 if (!win_ctx) Window_SDLFail("creating OpenGL context");
4325}
4326
4327void GLContext_Update(void) { }
4328cc_bool GLContext_TryRestore(void) { return true1; }
4329void GLContext_Free(void) {
4330 SDL_GL_DeleteContext(win_ctx);
4331 win_ctx = NULL((void*)0);
4332}
4333
4334void* GLContext_GetAddress(const char* function) {
4335 return SDL_GL_GetProcAddress(function);
4336}
4337
4338cc_bool GLContext_SwapBuffers(void) {
4339 SDL_GL_SwapWindow(win_handle);
4340 return true1;
4341}
4342
4343void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4344 SDL_GL_SetSwapInterval(vsync);
4345}
4346void GLContext_GetApiInfo(cc_string* info) { }
4347
4348
4349/*########################################################################################################################*
4350*-------------------------------------------------------EGL OpenGL--------------------------------------------------------*
4351*#########################################################################################################################*/
4352#elif defined CC_BUILD_EGL
4353#include <EGL/egl.h>
4354static EGLDisplay ctx_display;
4355static EGLContext ctx_context;
4356static EGLSurface ctx_surface;
4357static EGLConfig ctx_config;
4358static EGLint ctx_numConfig;
4359
4360#ifdef CC_BUILD_X11
4361static XVisualInfo GLContext_SelectVisual(struct GraphicsMode* mode) {
4362 XVisualInfo info;
4363 cc_result res;
4364 int screen = DefaultScreen(win_display)(((_XPrivDisplay)(win_display))->default_screen);
4365
4366 res = XMatchVisualInfo(win_display, screen, 24, TrueColor4, &info) ||
4367 XMatchVisualInfo(win_display, screen, 32, TrueColor4, &info);
4368
4369 if (!res) Logger_Abort("Selecting visual");
4370 return info;
4371}
4372#endif
4373
4374static void GLContext_InitSurface(void) {
4375 if (!win_handle) return; /* window not created or lost */
4376 ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, win_handle, NULL((void*)0));
4377
4378 if (!ctx_surface) return;
4379 eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
4380}
4381
4382static void GLContext_FreeSurface(void) {
4383 if (!ctx_surface) return;
4384 eglMakeCurrent(ctx_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
4385 eglDestroySurface(ctx_display, ctx_surface);
4386 ctx_surface = NULL((void*)0);
4387}
4388
4389void GLContext_Create(void) {
4390 static EGLint contextAttribs[3] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
4391 static EGLint attribs[19] = {
4392 EGL_RED_SIZE, 0, EGL_GREEN_SIZE, 0,
4393 EGL_BLUE_SIZE, 0, EGL_ALPHA_SIZE, 0,
4394 EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH24,
4395 EGL_STENCIL_SIZE, 0,
4396 EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
4397 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
4398 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
4399 EGL_NONE
4400 };
4401
4402 struct GraphicsMode mode;
4403 InitGraphicsMode(&mode);
4404 attribs[1] = mode.R; attribs[3] = mode.G;
4405 attribs[5] = mode.B; attribs[7] = mode.A;
4406
4407 ctx_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
4408 eglInitialize(ctx_display, NULL((void*)0), NULL((void*)0));
4409 eglBindAPI(EGL_OPENGL_ES_API);
4410 eglChooseConfig(ctx_display, attribs, &ctx_config, 1, &ctx_numConfig);
4411
4412 ctx_context = eglCreateContext(ctx_display, ctx_config, EGL_NO_CONTEXT, contextAttribs);
4413 GLContext_InitSurface();
4414}
4415
4416void GLContext_Update(void) {
4417 GLContext_FreeSurface();
4418 GLContext_InitSurface();
4419}
4420
4421cc_bool GLContext_TryRestore(void) {
4422 GLContext_FreeSurface();
4423 GLContext_InitSurface();
4424 return ctx_surface != NULL((void*)0);
4425}
4426
4427void GLContext_Free(void) {
4428 GLContext_FreeSurface();
4429 eglDestroyContext(ctx_display, ctx_context);
4430 eglTerminate(ctx_display);
4431}
4432
4433void* GLContext_GetAddress(const char* function) {
4434 return eglGetProcAddress(function);
4435}
4436
4437cc_bool GLContext_SwapBuffers(void) {
4438 EGLint err;
4439 if (!ctx_surface) return false0;
4440 if (eglSwapBuffers(ctx_display, ctx_surface)) return true1;
4441
4442 err = eglGetError();
4443 /* TODO: figure out what errors need to be handled here */
4444 Logger_Abort2(err, "Failed to swap buffers");
4445 return false0;
4446}
4447
4448void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4449 eglSwapInterval(ctx_display, vsync);
4450}
4451void GLContext_GetApiInfo(cc_string* info) { }
4452
4453
4454/*########################################################################################################################*
4455*-------------------------------------------------------WGL OpenGL--------------------------------------------------------*
4456*#########################################################################################################################*/
4457#elif defined CC_BUILD_WINGUI
4458static HGLRC ctx_handle;
4459static HDC ctx_DC;
4460typedef BOOL (WINAPI *FP_SWAPINTERVAL)(int interval);
4461static FP_SWAPINTERVAL wglSwapIntervalEXT;
4462
4463static void GLContext_SelectGraphicsMode(struct GraphicsMode* mode) {
4464 PIXELFORMATDESCRIPTOR pfd = { 0 };
4465 pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
4466 pfd.nVersion = 1;
4467 pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
4468 /* TODO: PFD_SUPPORT_COMPOSITION FLAG? CHECK IF IT WORKS ON XP */
4469 pfd.cColorBits = mode->R + mode->G + mode->B;
4470 pfd.cDepthBits = GLCONTEXT_DEFAULT_DEPTH24;
4471
4472 pfd.iPixelType = mode->IsIndexed ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
4473 pfd.cRedBits = mode->R;
4474 pfd.cGreenBits = mode->G;
4475 pfd.cBlueBits = mode->B;
4476 pfd.cAlphaBits = mode->A;
4477
4478 int modeIndex = ChoosePixelFormat(win_DC, &pfd);
4479 if (modeIndex == 0) { Logger_Abort("Requested graphics mode not available"); }
4480
4481 Mem_Set(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
4482 pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
4483 pfd.nVersion = 1;
4484
4485 DescribePixelFormat(win_DC, modeIndex, pfd.nSize, &pfd);
4486 if (!SetPixelFormat(win_DC, modeIndex, &pfd)) {
4487 Logger_Abort2(GetLastError(), "SetPixelFormat failed");
4488 }
4489}
4490
4491void GLContext_Create(void) {
4492 struct GraphicsMode mode;
4493 InitGraphicsMode(&mode);
4494 GLContext_SelectGraphicsMode(&mode);
4495
4496 ctx_handle = wglCreateContext(win_DC);
4497 if (!ctx_handle) ctx_handle = wglCreateContext(win_DC);
4498
4499 if (!ctx_handle) {
4500 Logger_Abort2(GetLastError(), "Failed to create OpenGL context");
4501 }
4502
4503 if (!wglMakeCurrent(win_DC, ctx_handle)) {
4504 Logger_Abort2(GetLastError(), "Failed to make OpenGL context current");
4505 }
4506
4507 ctx_DC = wglGetCurrentDC();
4508 wglSwapIntervalEXT = (FP_SWAPINTERVAL)GLContext_GetAddress("wglSwapIntervalEXT");
4509}
4510
4511void GLContext_Update(void) { }
4512cc_bool GLContext_TryRestore(void) { return true1; }
4513void GLContext_Free(void) {
4514 if (!ctx_handle) return;
4515 wglDeleteContext(ctx_handle);
4516 ctx_handle = NULL((void*)0);
4517}
4518
4519void* GLContext_GetAddress(const char* function) {
4520 void* addr = (void*)wglGetProcAddress(function);
4521 return GLContext_IsInvalidAddress(addr)(addr == (void*)0 || addr == (void*)1 || addr == (void*)-1 ||
addr == (void*)2)
? NULL((void*)0) : addr;
4522}
4523
4524cc_bool GLContext_SwapBuffers(void) {
4525 if (!SwapBuffers(ctx_DC)) Logger_Abort2(GetLastError(), "Failed to swap buffers");
4526 return true1;
4527}
4528
4529void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4530 if (!wglSwapIntervalEXT) return;
4531 wglSwapIntervalEXT(vsync);
4532}
4533void GLContext_GetApiInfo(cc_string* info) { }
4534
4535
4536/*########################################################################################################################*
4537*-------------------------------------------------------glX OpenGL--------------------------------------------------------*
4538*#########################################################################################################################*/
4539#elif defined CC_BUILD_X11
4540#include <GL/glx.h>
4541static GLXContext ctx_handle;
4542typedef int (*FP_SWAPINTERVAL)(int interval);
4543typedef Boolint (*FP_QUERYRENDERER)(int attribute, unsigned int* value);
4544static FP_SWAPINTERVAL swapIntervalMESA, swapIntervalSGI;
4545static FP_QUERYRENDERER queryRendererMESA;
4546
4547void GLContext_Create(void) {
4548 static const cc_string vsync_mesa = String_FromConst("GLX_MESA_swap_control"){ "GLX_MESA_swap_control", (sizeof("GLX_MESA_swap_control") -
1), (sizeof("GLX_MESA_swap_control") - 1)}
;
4549 static const cc_string vsync_sgi = String_FromConst("GLX_SGI_swap_control"){ "GLX_SGI_swap_control", (sizeof("GLX_SGI_swap_control") - 1
), (sizeof("GLX_SGI_swap_control") - 1)}
;
4550 static const cc_string info_mesa = String_FromConst("GLX_MESA_query_renderer"){ "GLX_MESA_query_renderer", (sizeof("GLX_MESA_query_renderer"
) - 1), (sizeof("GLX_MESA_query_renderer") - 1)}
;
4551
4552 const char* raw_exts;
4553 cc_string exts;
4554 ctx_handle = glXCreateContext(win_display, &win_visual, NULL((void*)0), true1);
4555
4556 if (!ctx_handle) {
4557 Platform_LogConst("Context create failed. Trying indirect...");
4558 ctx_handle = glXCreateContext(win_display, &win_visual, NULL((void*)0), false0);
4559 }
4560 if (!ctx_handle) Logger_Abort("Failed to create OpenGL context");
4561
4562 if (!glXIsDirect(win_display, ctx_handle)) {
4563 Platform_LogConst("== WARNING: Context is not direct ==");
4564 }
4565 if (!glXMakeCurrent(win_display, win_handle, ctx_handle)) {
4566 Logger_Abort("Failed to make OpenGL context current.");
4567 }
4568
4569 /* GLX may return non-null function pointers that don't actually work */
4570 /* So we need to manually check the extensions string for support */
4571 raw_exts = glXQueryExtensionsString(win_display, DefaultScreen(win_display)(((_XPrivDisplay)(win_display))->default_screen));
4572 exts = String_FromReadonly(raw_exts);
4573
4574 if (String_CaselessContains(&exts, &vsync_mesa)) {
4575 swapIntervalMESA = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalMESA");
4576 }
4577 if (String_CaselessContains(&exts, &vsync_sgi)) {
4578 swapIntervalSGI = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalSGI");
4579 }
4580 if (String_CaselessContains(&exts, &info_mesa)) {
4581 queryRendererMESA = (FP_QUERYRENDERER)GLContext_GetAddress("glXQueryCurrentRendererIntegerMESA");
4582 }
4583}
4584
4585void GLContext_Update(void) { }
4586cc_bool GLContext_TryRestore(void) { return true1; }
4587void GLContext_Free(void) {
4588 if (!ctx_handle) return;
4589 glXMakeCurrent(win_display, None0L, NULL((void*)0));
4590 glXDestroyContext(win_display, ctx_handle);
4591 ctx_handle = NULL((void*)0);
4592}
4593
4594void* GLContext_GetAddress(const char* function) {
4595 void* addr = (void*)glXGetProcAddress((const GLubyte*)function);
4596 return GLContext_IsInvalidAddress(addr)(addr == (void*)0 || addr == (void*)1 || addr == (void*)-1 ||
addr == (void*)2)
? NULL((void*)0) : addr;
4597}
4598
4599cc_bool GLContext_SwapBuffers(void) {
4600 glXSwapBuffers(win_display, win_handle);
4601 return true1;
4602}
4603
4604void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4605 int res = 0;
4606 if (swapIntervalMESA) {
4607 res = swapIntervalMESA(vsync);
4608 } else if (swapIntervalSGI) {
4609 res = swapIntervalSGI(vsync);
4610 }
4611 if (res) Platform_Log1("Set VSync failed, error: %i", &res);
4612}
4613
4614void GLContext_GetApiInfo(cc_string* info) {
4615 unsigned int vram, acc;
4616 if (!queryRendererMESA) return;
4617
4618 queryRendererMESA(0x8186, &acc);
4619 queryRendererMESA(0x8187, &vram);
4620 String_Format2(info, "VRAM: %i MB, %c", &vram,
4621 acc ? "HW accelerated" : "no HW acceleration");
4622}
4623
4624static void GetAttribs(struct GraphicsMode* mode, int* attribs, int depth) {
4625 int i = 0;
4626 /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.opengl/doc/openglrf/glXChooseFBConfig.htm%23glxchoosefbconfig */
4627 /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.opengl/doc/openglrf/glXChooseVisual.htm%23b5c84be452rree */
4628 /* for the attribute declarations. Note that the attributes are different than those used in glxChooseVisual */
4629
4630 if (!mode->IsIndexed) { attribs[i++] = GLX_RGBA4; }
4631 attribs[i++] = GLX_RED_SIZE8; attribs[i++] = mode->R;
4632 attribs[i++] = GLX_GREEN_SIZE9; attribs[i++] = mode->G;
4633 attribs[i++] = GLX_BLUE_SIZE10; attribs[i++] = mode->B;
4634 attribs[i++] = GLX_ALPHA_SIZE11; attribs[i++] = mode->A;
4635 attribs[i++] = GLX_DEPTH_SIZE12; attribs[i++] = depth;
4636
4637 attribs[i++] = GLX_DOUBLEBUFFER5;
4638 attribs[i++] = 0;
4639}
4640
4641static XVisualInfo GLContext_SelectVisual(struct GraphicsMode* mode) {
4642 int attribs[20];
4643 int major, minor;
4644 XVisualInfo* visual = NULL((void*)0);
4645
4646 int fbcount, screen;
4647 GLXFBConfig* fbconfigs;
4648 XVisualInfo info;
4649
4650 GetAttribs(mode, attribs, GLCONTEXT_DEFAULT_DEPTH24);
4651 if (!glXQueryVersion(win_display, &major, &minor)) {
4652 Logger_Abort("glXQueryVersion failed");
4653 }
4654 screen = DefaultScreen(win_display)(((_XPrivDisplay)(win_display))->default_screen);
4655
4656 if (major >= 1 && minor >= 3) {
4657 /* ChooseFBConfig returns an array of GLXFBConfig opaque structures */
4658 fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount);
4659 if (fbconfigs && fbcount) {
4660 /* Use the first GLXFBConfig from the fbconfigs array (best match) */
4661 visual = glXGetVisualFromFBConfig(win_display, *fbconfigs);
4662 XFree(fbconfigs);
4663 }
4664 }
4665
4666 if (!visual) {
4667 Platform_LogConst("Falling back to glXChooseVisual.");
4668 visual = glXChooseVisual(win_display, screen, attribs);
4669 }
4670 /* Some really old devices will only supply 16 bit depths */
4671 if (!visual) {
4672 GetAttribs(mode, attribs, 16);
4673 visual = glXChooseVisual(win_display, screen, attribs);
4674 }
4675 if (!visual) Logger_Abort("Requested GraphicsMode not available.");
4676
4677 info = *visual;
4678 XFree(visual);
4679 return info;
4680}
4681
4682
4683/*########################################################################################################################*
4684*-------------------------------------------------------AGL OpenGL--------------------------------------------------------*
4685*#########################################################################################################################*/
4686#elif defined CC_BUILD_CARBON
4687#include <AGL/agl.h>
4688
4689static AGLContext ctx_handle;
4690static cc_bool ctx_firstFullscreen;
4691static int ctx_windowWidth, ctx_windowHeight;
4692
4693static void GLContext_Check(int code, const char* place) {
4694 cc_result res;
4695 if (code) return;
4696
4697 res = aglGetError();
4698 if (res) Logger_Abort2(res, place);
4699}
4700
4701static void GLContext_MakeCurrent(void) {
4702 int code = aglSetCurrentContext(ctx_handle);
4703 GLContext_Check(code, "Setting GL context");
4704}
4705
4706static void GLContext_SetDrawable(void) {
4707 CGrafPtr windowPort = GetWindowPort(win_handle);
4708 int code = aglSetDrawable(ctx_handle, windowPort);
4709 GLContext_Check(code, "Attaching GL context");
4710}
4711
4712static void GLContext_GetAttribs(struct GraphicsMode* mode, GLint* attribs, cc_bool fullscreen) {
4713 int i = 0;
4714
4715 if (!mode->IsIndexed) { attribs[i++] = AGL_RGBA; }
4716 attribs[i++] = AGL_RED_SIZE; attribs[i++] = mode->R;
4717 attribs[i++] = AGL_GREEN_SIZE; attribs[i++] = mode->G;
4718 attribs[i++] = AGL_BLUE_SIZE; attribs[i++] = mode->B;
4719 attribs[i++] = AGL_ALPHA_SIZE; attribs[i++] = mode->A;
4720 attribs[i++] = AGL_DEPTH_SIZE; attribs[i++] = GLCONTEXT_DEFAULT_DEPTH24;
4721
4722 attribs[i++] = AGL_DOUBLEBUFFER;
4723 if (fullscreen) { attribs[i++] = AGL_FULLSCREEN; }
4724 attribs[i++] = 0;
4725}
4726
4727static cc_result GLContext_UnsetFullscreen(void) {
4728 int code;
4729 Platform_LogConst("Unsetting fullscreen.");
4730
4731 code = aglSetDrawable(ctx_handle, NULL((void*)0));
4732 if (!code) return aglGetError();
4733 /* TODO: I don't think this is necessary */
4734 code = aglUpdateContext(ctx_handle);
4735 if (!code) return aglGetError();
4736
4737 CGDisplayRelease(CGMainDisplayID());
4738 GLContext_SetDrawable();
4739
4740 win_fullscreen = false0;
4741 /* TODO: Eliminate this if possible */
4742 Window_SetSize(ctx_windowWidth, ctx_windowHeight);
4743 return 0;
4744}
4745
4746static cc_result GLContext_SetFullscreen(void) {
4747 int width = DisplayInfo.Width;
4748 int height = DisplayInfo.Height;
4749 int code;
4750
4751 Platform_LogConst("Switching to fullscreen");
4752 /* TODO: Does aglSetFullScreen capture the screen anyways? */
4753 CGDisplayCapture(CGMainDisplayID());
4754
4755 if (!aglSetFullScreen(ctx_handle, width, height, 0, 0)) {
4756 code = aglGetError();
4757 GLContext_UnsetFullscreen();
4758 return code;
4759 }
4760 /* TODO: Do we really need to call this? */
4761 GLContext_MakeCurrent();
4762
4763 /* This is a weird hack to workaround a bug where the first time a context */
4764 /* is made fullscreen, we just end up with a blank screen. So we undo it as fullscreen */
4765 /* and redo it as fullscreen. */
4766 /* TODO: We really should'd need to do this. Need to debug on real hardware. */
4767 if (!ctx_firstFullscreen) {
4768 ctx_firstFullscreen = true1;
4769 GLContext_UnsetFullscreen();
4770 return GLContext_SetFullscreen();
4771 }
4772
4773 win_fullscreen = true1;
4774 ctx_windowWidth = WindowInfo.Width;
4775 ctx_windowHeight = WindowInfo.Height;
4776
4777 windowX = DisplayInfo.X; WindowInfo.Width = DisplayInfo.Width;
4778 windowY = DisplayInfo.Y; WindowInfo.Height = DisplayInfo.Height;
4779 return 0;
4780}
4781
4782void GLContext_Create(void) {
4783 GLint attribs[20];
4784 AGLPixelFormat fmt;
4785 GDHandle gdevice;
4786 OSStatus res;
4787 struct GraphicsMode mode;
4788 InitGraphicsMode(&mode);
4789
4790 /* Initially try creating fullscreen compatible context */
4791 res = DMGetGDeviceByDisplayID(CGMainDisplayID(), &gdevice, false0);
4792 if (res) Logger_Abort2(res, "Getting display device failed");
4793
4794 GLContext_GetAttribs(&mode, attribs, true1);
4795 fmt = aglChoosePixelFormat(&gdevice, 1, attribs);
4796 res = aglGetError();
4797
4798 /* Try again with non-compatible context if that fails */
4799 if (!fmt || res == AGL_BAD_PIXELFMT) {
4800 Platform_LogConst("Failed to create full screen pixel format.");
4801 Platform_LogConst("Trying again to create a non-fullscreen pixel format.");
4802
4803 GLContext_GetAttribs(&mode, attribs, false0);
4804 fmt = aglChoosePixelFormat(NULL((void*)0), 0, attribs);
4805 res = aglGetError();
4806 }
4807 if (res) Logger_Abort2(res, "Choosing pixel format");
4808
4809 ctx_handle = aglCreateContext(fmt, NULL((void*)0));
4810 GLContext_Check(0, "Creating GL context");
4811
4812 aglDestroyPixelFormat(fmt);
4813 GLContext_Check(0, "Destroying pixel format");
4814
4815 GLContext_SetDrawable();
4816 GLContext_Update();
4817 GLContext_MakeCurrent();
4818}
4819
4820void GLContext_Update(void) {
4821 if (win_fullscreen) return;
4822 GLContext_SetDrawable();
4823 aglUpdateContext(ctx_handle);
4824}
4825cc_bool GLContext_TryRestore(void) { return true1; }
4826
4827void GLContext_Free(void) {
4828 if (!ctx_handle) return;
4829 aglSetCurrentContext(NULL((void*)0));
4830 aglDestroyContext(ctx_handle);
4831 ctx_handle = NULL((void*)0);
4832}
4833
4834void* GLContext_GetAddress(const char* function) {
4835 static const cc_string glPath = String_FromConst("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"){ "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
, (sizeof("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
) - 1), (sizeof("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
) - 1)}
;
4836 static void* lib;
4837 void* addr;
4838
4839 if (!lib) lib = DynamicLib_Load2(&glPath);
4840 addr = DynamicLib_Get2(lib, function);
4841 return GLContext_IsInvalidAddress(addr)(addr == (void*)0 || addr == (void*)1 || addr == (void*)-1 ||
addr == (void*)2)
? NULL((void*)0) : addr;
4842}
4843
4844cc_bool GLContext_SwapBuffers(void) {
4845 aglSwapBuffers(ctx_handle);
4846 GLContext_Check(0, "Swapping buffers");
4847 return true1;
4848}
4849
4850void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4851 int value = vsync ? 1 : 0;
4852 aglSetInteger(ctx_handle, AGL_SWAP_INTERVAL, &value);
4853}
4854void GLContext_GetApiInfo(cc_string* info) { }
4855
4856
4857/*########################################################################################################################*
4858*--------------------------------------------------------NSOpenGL---------------------------------------------------------*
4859*#########################################################################################################################*/
4860#elif defined CC_BUILD_COCOA
4861#define NSOpenGLPFADoubleBuffer 5
4862#define NSOpenGLPFAColorSize 8
4863#define NSOpenGLPFADepthSize 12
4864#define NSOpenGLPFAFullScreen 54
4865#define NSOpenGLContextParameterSwapInterval 222
4866
4867static id ctxHandle;
4868static id MakePixelFormat(struct GraphicsMode* mode, cc_bool fullscreen) {
4869 id fmt;
4870 uint32_t attribs[7] = {
4871 NSOpenGLPFAColorSize, 0,
4872 NSOpenGLPFADepthSize, GLCONTEXT_DEFAULT_DEPTH24,
4873 NSOpenGLPFADoubleBuffer, 0, 0
4874 };
4875
4876 attribs[1] = mode->R + mode->G + mode->B + mode->A;
4877 attribs[5] = fullscreen ? NSOpenGLPFAFullScreen : 0;
4878 fmt = objc_msgSend((id)objc_getClass("NSOpenGLPixelFormat"), sel_registerName("alloc"));
4879 return objc_msgSend(fmt, sel_registerName("initWithAttributes:"), attribs);
4880}
4881
4882void GLContext_Create(void) {
4883 struct GraphicsMode mode;
4884 id view, fmt;
4885
4886 InitGraphicsMode(&mode);
4887 fmt = MakePixelFormat(&mode, true1);
4888 if (!fmt) {
4889 Platform_LogConst("Failed to create full screen pixel format.");
4890 Platform_LogConst("Trying again to create a non-fullscreen pixel format.");
4891 fmt = MakePixelFormat(&mode, false0);
4892 }
4893 if (!fmt) Logger_Abort("Choosing pixel format");
4894
4895 ctxHandle = objc_msgSend((id)objc_getClass("NSOpenGLContext"), sel_registerName("alloc"));
4896 ctxHandle = objc_msgSend(ctxHandle, sel_registerName("initWithFormat:shareContext:"), fmt, NULL((void*)0));
4897 if (!ctxHandle) Logger_Abort("Failed to create OpenGL context");
4898
4899 objc_msgSend(ctxHandle, sel_registerName("setView:"), viewHandle);
4900 objc_msgSend(fmt, sel_registerName("release"));
4901 objc_msgSend(ctxHandle, sel_registerName("makeCurrentContext"));
4902 objc_msgSend(ctxHandle, selUpdate);
4903}
4904
4905void GLContext_Update(void) {
4906 // TODO: Why does this crash on resizing
4907 objc_msgSend(ctxHandle, selUpdate);
4908}
4909cc_bool GLContext_TryRestore(void) { return true1; }
4910
4911void GLContext_Free(void) {
4912 objc_msgSend((id)objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext"));
4913 objc_msgSend(ctxHandle, sel_registerName("clearDrawable"));
4914 objc_msgSend(ctxHandle, sel_registerName("release"));
4915}
4916
4917void* GLContext_GetAddress(const char* function) {
4918 static const cc_string glPath = String_FromConst("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"){ "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
, (sizeof("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
) - 1), (sizeof("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
) - 1)}
;
4919 static void* lib;
4920 void* addr;
4921
4922 if (!lib) lib = DynamicLib_Load2(&glPath);
4923 addr = DynamicLib_Get2(lib, function);
4924 return GLContext_IsInvalidAddress(addr)(addr == (void*)0 || addr == (void*)1 || addr == (void*)-1 ||
addr == (void*)2)
? NULL((void*)0) : addr;
4925}
4926
4927cc_bool GLContext_SwapBuffers(void) {
4928 objc_msgSend(ctxHandle, selFlushBuffer);
4929 return true1;
4930}
4931
4932void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4933 int value = vsync ? 1 : 0;
4934 objc_msgSend(ctxHandle, sel_registerName("setValues:forParameter:"), &value, NSOpenGLContextParameterSwapInterval);
4935}
4936void GLContext_GetApiInfo(cc_string* info) { }
4937
4938
4939/*########################################################################################################################*
4940*------------------------------------------------Emscripten WebGL context-------------------------------------------------*
4941*#########################################################################################################################*/
4942#elif defined CC_BUILD_WEB
4943#include "Graphics.h"
4944static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx_handle;
4945
4946static EM_BOOL GLContext_OnLost(int eventType, const void *reserved, void *userData) {
4947 Gfx_LoseContext("WebGL context lost");
4948 return 1;
4949}
4950
4951void GLContext_Create(void) {
4952 EmscriptenWebGLContextAttributes attribs;
4953 emscripten_webgl_init_context_attributes(&attribs);
4954 attribs.alpha = false0;
4955 attribs.depth = true1;
4956 attribs.stencil = false0;
4957 attribs.antialias = false0;
4958
4959 ctx_handle = emscripten_webgl_create_context("#canvas", &attribs);
4960 if (!ctx_handle) Window_ShowDialog("WebGL unsupported", "WebGL is required to run ClassiCube");
4961
4962 emscripten_webgl_make_context_current(ctx_handle);
4963 emscripten_set_webglcontextlost_callback("#canvas", NULL((void*)0), 0, GLContext_OnLost);
4964}
4965
4966void GLContext_Update(void) {
4967 /* TODO: do we need to do something here.... ? */
4968}
4969cc_bool GLContext_TryRestore(void) {
4970 return !emscripten_is_webgl_context_lost(0);
4971}
4972
4973void GLContext_Free(void) {
4974 emscripten_webgl_destroy_context(ctx_handle);
4975 emscripten_set_webglcontextlost_callback("#canvas", NULL((void*)0), 0, NULL((void*)0));
4976}
4977
4978void* GLContext_GetAddress(const char* function) { return NULL((void*)0); }
4979cc_bool GLContext_SwapBuffers(void) { return true1; /* Browser implicitly does this */ }
4980
4981void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
4982 if (vsync) {
4983 emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
4984 } else {
4985 emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, (int)minFrameMs);
4986 }
4987}
4988
4989void GLContext_GetApiInfo(cc_string* info) {
4990 char buffer[NATIVE_STR_LEN600];
4991 int len;
4992
4993 EM_ASM_({
4994 var dbg = GLctx.getExtension('WEBGL_debug_renderer_info');
4995 var str = dbg ? GLctx.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : "";
4996 stringToUTF8(str, $0, $1);
4997 }, buffer, NATIVE_STR_LEN600);
4998
4999 len = String_CalcLen(buffer, NATIVE_STR_LEN600);
5000 if (!len) return;
5001 String_AppendConst(info, "GPU: ");
5002 String_AppendUtf8(info, buffer, len);
5003}
5004#endif
5005#endif