Bug Summary

File:Window.c
Warning:line 4563, column 9
Dereference of null pointer (loaded from variable 'visual')

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