v / thirdparty / sokol
Raw file | 12530 loc (11472 sloc) | 475.3 KB | Latest commit hash e854051c1
1#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL)
2#define SOKOL_APP_IMPL
3#endif
4#ifndef SOKOL_APP_INCLUDED
5
6/*
7 V language IMPORTANT NOTE: all the V patches in this code, are marked with:
8 // __v_ start
9 // __v_ end
10*/
11
12/*
13 sokol_app.h -- cross-platform application wrapper
14
15 Project URL: https://github.com/floooh/sokol
16
17 Do this:
18 #define SOKOL_IMPL or
19 #define SOKOL_APP_IMPL
20 before you include this file in *one* C or C++ file to create the
21 implementation.
22
23 In the same place define one of the following to select the 3D-API
24 which should be initialized by sokol_app.h (this must also match
25 the backend selected for sokol_gfx.h if both are used in the same
26 project):
27
28 #define SOKOL_GLCORE33
29 #define SOKOL_GLES2
30 #define SOKOL_GLES3
31 #define SOKOL_D3D11
32 #define SOKOL_METAL
33 #define SOKOL_WGPU
34
35 Optionally provide the following defines with your own implementations:
36
37 SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
38 SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false))
39 SOKOL_ABORT() - called after an unrecoverable error (default: abort())
40 SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain
41 SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function
42 SOKOL_APP_API_DECL - public function declaration prefix (default: extern)
43 SOKOL_API_DECL - same as SOKOL_APP_API_DECL
44 SOKOL_API_IMPL - public function implementation prefix (default: -)
45
46 Optionally define the following to force debug checks and validations
47 even in release mode:
48
49 SOKOL_DEBUG - by default this is defined if _DEBUG is defined
50
51 If sokol_app.h is compiled as a DLL, define the following before
52 including the declaration or implementation:
53
54 SOKOL_DLL
55
56 On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport)
57 or __declspec(dllimport) as needed.
58
59 On Linux, SOKOL_GLCORE33 can use either GLX or EGL.
60 GLX is default, set SOKOL_FORCE_EGL to override.
61
62 For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp
63
64 Portions of the Windows and Linux GL initialization, event-, icon- etc... code
65 have been taken from GLFW (http://www.glfw.org/)
66
67 iOS onscreen keyboard support 'inspired' by libgdx.
68
69 Link with the following system libraries:
70
71 - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit
72 - on macOS with GL: Cocoa, QuartzCore, OpenGL
73 - on iOS with Metal: Foundation, UIKit, Metal, MetalKit
74 - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit
75 - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?)
76 - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?)
77 - on Android: GLESv3, EGL, log, android
78 - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib
79 - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined
80 - link with the following libs: -lkernel32 -luser32 -lshell32
81 - additionally with the GL backend: -lgdi32
82 - additionally with the D3D11 backend: -ld3d11 -ldxgi
83
84 On Linux, you also need to use the -pthread compiler and linker option, otherwise weird
85 things will happen, see here for details: https://github.com/floooh/sokol/issues/376
86
87 Building for UWP requires a recent Visual Studio toolchain and Windows SDK
88 (at least VS2019 and Windows SDK 10.0.19041.0). When the UWP backend is
89 selected, the sokol_app.h implementation must be compiled as C++17.
90
91 On macOS and iOS, the implementation must be compiled as Objective-C.
92
93 FEATURE OVERVIEW
94 ================
95 sokol_app.h provides a minimalistic cross-platform API which
96 implements the 'application-wrapper' parts of a 3D application:
97
98 - a common application entry function
99 - creates a window and 3D-API context/device with a 'default framebuffer'
100 - makes the rendered frame visible
101 - provides keyboard-, mouse- and low-level touch-events
102 - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android
103 - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2
104
105 FEATURE/PLATFORM MATRIX
106 =======================
107 | Windows | macOS | Linux | iOS | Android | UWP | HTML5
108 --------------------+---------+-------+-------+-------+---------+------+-------
109 gl 3.x | YES | YES | YES | --- | --- | --- | ---
110 gles2/webgl | --- | --- | YES(2)| YES | YES | --- | YES
111 gles3/webgl2 | --- | --- | YES(2)| YES | YES | --- | YES
112 metal | --- | YES | --- | YES | --- | --- | ---
113 d3d11 | YES | --- | --- | --- | --- | YES | ---
114 KEY_DOWN | YES | YES | YES | SOME | TODO | YES | YES
115 KEY_UP | YES | YES | YES | SOME | TODO | YES | YES
116 CHAR | YES | YES | YES | YES | TODO | YES | YES
117 MOUSE_DOWN | YES | YES | YES | --- | --- | YES | YES
118 MOUSE_UP | YES | YES | YES | --- | --- | YES | YES
119 MOUSE_SCROLL | YES | YES | YES | --- | --- | YES | YES
120 MOUSE_MOVE | YES | YES | YES | --- | --- | YES | YES
121 MOUSE_ENTER | YES | YES | YES | --- | --- | YES | YES
122 MOUSE_LEAVE | YES | YES | YES | --- | --- | YES | YES
123 TOUCHES_BEGAN | --- | --- | --- | YES | YES | TODO | YES
124 TOUCHES_MOVED | --- | --- | --- | YES | YES | TODO | YES
125 TOUCHES_ENDED | --- | --- | --- | YES | YES | TODO | YES
126 TOUCHES_CANCELLED | --- | --- | --- | YES | YES | TODO | YES
127 RESIZED | YES | YES | YES | YES | YES | YES | YES
128 ICONIFIED | YES | YES | YES | --- | --- | YES | ---
129 RESTORED | YES | YES | YES | --- | --- | YES | ---
130 FOCUSED | YES | YES | YES | --- | --- | --- | YES
131 UNFOCUSED | YES | YES | YES | --- | --- | --- | YES
132 SUSPENDED | --- | --- | --- | YES | YES | YES | TODO
133 RESUMED | --- | --- | --- | YES | YES | YES | TODO
134 QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | YES
135 IME | TODO | TODO? | TODO | ??? | TODO | --- | ???
136 key repeat flag | YES | YES | YES | --- | --- | YES | YES
137 windowed | YES | YES | YES | --- | --- | YES | YES
138 fullscreen | YES | YES | YES | YES | YES | YES | ---
139 mouse hide | YES | YES | YES | --- | --- | YES | YES
140 mouse lock | YES | YES | YES | --- | --- | TODO | YES
141 set cursor type | YES | YES | YES | --- | --- | YES | YES
142 screen keyboard | --- | --- | --- | YES | TODO | TODO | YES
143 swap interval | YES | YES | YES | YES | TODO | --- | YES
144 high-dpi | YES | YES | TODO | YES | YES | YES | YES
145 clipboard | YES | YES | TODO | --- | --- | TODO | YES
146 MSAA | YES | YES | YES | YES | YES | TODO | YES
147 drag'n'drop | YES | YES | YES | --- | --- | TODO | YES
148 window icon | YES | YES(1)| YES | --- | --- | TODO | YES
149
150 (1) macOS has no regular window icons, instead the dock icon is changed
151 (2) supported with EGL only (not GLX)
152
153 STEP BY STEP
154 ============
155 --- Add a sokol_main() function to your code which returns a sapp_desc structure
156 with initialization parameters and callback function pointers. This
157 function is called very early, usually at the start of the
158 platform's entry function (e.g. main or WinMain). You should do as
159 little as possible here, since the rest of your code might be called
160 from another thread (this depends on the platform):
161
162 sapp_desc sokol_main(int argc, char* argv[]) {
163 return (sapp_desc) {
164 .width = 640,
165 .height = 480,
166 .init_cb = my_init_func,
167 .frame_cb = my_frame_func,
168 .cleanup_cb = my_cleanup_func,
169 .event_cb = my_event_func,
170 ...
171 };
172 }
173
174 There are many more setup parameters, but these are the most important.
175 For a complete list search for the sapp_desc structure declaration
176 below.
177
178 DO NOT call any sokol-app function from inside sokol_main(), since
179 sokol-app will not be initialized at this point.
180
181 The .width and .height parameters are the preferred size of the 3D
182 rendering canvas. The actual size may differ from this depending on
183 platform and other circumstances. Also the canvas size may change at
184 any time (for instance when the user resizes the application window,
185 or rotates the mobile device). You can just keep .width and .height
186 zero-initialized to open a default-sized window (what "default-size"
187 exactly means is platform-specific, but usually it's a size that covers
188 most of, but not all, of the display).
189
190 All provided function callbacks will be called from the same thread,
191 but this may be different from the thread where sokol_main() was called.
192
193 .init_cb (void (*)(void))
194 This function is called once after the application window,
195 3D rendering context and swap chain have been created. The
196 function takes no arguments and has no return value.
197 .frame_cb (void (*)(void))
198 This is the per-frame callback, which is usually called 60
199 times per second. This is where your application would update
200 most of its state and perform all rendering.
201 .cleanup_cb (void (*)(void))
202 The cleanup callback is called once right before the application
203 quits.
204 .event_cb (void (*)(const sapp_event* event))
205 The event callback is mainly for input handling, but is also
206 used to communicate other types of events to the application. Keep the
207 event_cb struct member zero-initialized if your application doesn't require
208 event handling.
209 .fail_cb (void (*)(const char* msg))
210 The fail callback is called when a fatal error is encountered
211 during start which doesn't allow the program to continue.
212 Providing a callback here gives you a chance to show an error message
213 to the user. The default behaviour is SAPP_LOG(msg)
214
215 As you can see, those 'standard callbacks' don't have a user_data
216 argument, so any data that needs to be preserved between callbacks
217 must live in global variables. If keeping state in global variables
218 is not an option, there's an alternative set of callbacks with
219 an additional user_data pointer argument:
220
221 .user_data (void*)
222 The user-data argument for the callbacks below
223 .init_userdata_cb (void (*)(void* user_data))
224 .frame_userdata_cb (void (*)(void* user_data))
225 .cleanup_userdata_cb (void (*)(void* user_data))
226 .event_userdata_cb (void(*)(const sapp_event* event, void* user_data))
227 .fail_userdata_cb (void(*)(const char* msg, void* user_data))
228 These are the user-data versions of the callback functions. You
229 can mix those with the standard callbacks that don't have the
230 user_data argument.
231
232 The function sapp_userdata() can be used to query the user_data
233 pointer provided in the sapp_desc struct.
234
235 You can also call sapp_query_desc() to get a copy of the
236 original sapp_desc structure.
237
238 NOTE that there's also an alternative compile mode where sokol_app.h
239 doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY.
240
241 --- Implement the initialization callback function (init_cb), this is called
242 once after the rendering surface, 3D API and swap chain have been
243 initialized by sokol_app. All sokol-app functions can be called
244 from inside the initialization callback, the most useful functions
245 at this point are:
246
247 int sapp_width(void)
248 int sapp_height(void)
249 Returns the current width and height of the default framebuffer in pixels,
250 this may change from one frame to the next, and it may be different
251 from the initial size provided in the sapp_desc struct.
252
253 float sapp_widthf(void)
254 float sapp_heightf(void)
255 These are alternatives to sapp_width() and sapp_height() which return
256 the default framebuffer size as float values instead of integer. This
257 may help to prevent casting back and forth between int and float
258 in more strongly typed languages than C and C++.
259
260 double sapp_frame_duration(void)
261 Returns the frame duration in seconds averaged over a number of
262 frames to smooth out any jittering spikes.
263
264 int sapp_color_format(void)
265 int sapp_depth_format(void)
266 The color and depth-stencil pixelformats of the default framebuffer,
267 as integer values which are compatible with sokol-gfx's
268 sg_pixel_format enum (so that they can be plugged directly in places
269 where sg_pixel_format is expected). Possible values are:
270
271 23 == SG_PIXELFORMAT_RGBA8
272 27 == SG_PIXELFORMAT_BGRA8
273 41 == SG_PIXELFORMAT_DEPTH
274 42 == SG_PIXELFORMAT_DEPTH_STENCIL
275
276 int sapp_sample_count(void)
277 Return the MSAA sample count of the default framebuffer.
278
279 bool sapp_gles2(void)
280 Returns true if a GLES2 or WebGL context has been created. This
281 is useful when a GLES3/WebGL2 context was requested but is not
282 available so that sokol_app.h had to fallback to GLES2/WebGL.
283
284 const void* sapp_metal_get_device(void)
285 const void* sapp_metal_get_renderpass_descriptor(void)
286 const void* sapp_metal_get_drawable(void)
287 If the Metal backend has been selected, these functions return pointers
288 to various Metal API objects required for rendering, otherwise
289 they return a null pointer. These void pointers are actually
290 Objective-C ids converted with a (ARC) __bridge cast so that
291 the ids can be tunnel through C code. Also note that the returned
292 pointers to the renderpass-descriptor and drawable may change from one
293 frame to the next, only the Metal device object is guaranteed to
294 stay the same.
295
296 const void* sapp_macos_get_window(void)
297 On macOS, get the NSWindow object pointer, otherwise a null pointer.
298 Before being used as Objective-C object, the void* must be converted
299 back with a (ARC) __bridge cast.
300
301 const void* sapp_ios_get_window(void)
302 On iOS, get the UIWindow object pointer, otherwise a null pointer.
303 Before being used as Objective-C object, the void* must be converted
304 back with a (ARC) __bridge cast.
305
306 const void* sapp_win32_get_hwnd(void)
307 On Windows, get the window's HWND, otherwise a null pointer. The
308 HWND has been cast to a void pointer in order to be tunneled
309 through code which doesn't include Windows.h.
310
311 const void* sapp_d3d11_get_device(void)
312 const void* sapp_d3d11_get_device_context(void)
313 const void* sapp_d3d11_get_render_target_view(void)
314 const void* sapp_d3d11_get_depth_stencil_view(void)
315 Similar to the sapp_metal_* functions, the sapp_d3d11_* functions
316 return pointers to D3D11 API objects required for rendering,
317 only if the D3D11 backend has been selected. Otherwise they
318 return a null pointer. Note that the returned pointers to the
319 render-target-view and depth-stencil-view may change from one
320 frame to the next!
321
322 const void* sapp_wgpu_get_device(void)
323 const void* sapp_wgpu_get_render_view(void)
324 const void* sapp_wgpu_get_resolve_view(void)
325 const void* sapp_wgpu_get_depth_stencil_view(void)
326 These are the WebGPU-specific functions to get the WebGPU
327 objects and values required for rendering. If sokol_app.h
328 is not compiled with SOKOL_WGPU, these functions return null.
329
330 const void* sapp_android_get_native_activity(void);
331 On Android, get the native activity ANativeActivity pointer, otherwise
332 a null pointer.
333
334 --- Implement the frame-callback function, this function will be called
335 on the same thread as the init callback, but might be on a different
336 thread than the sokol_main() function. Note that the size of
337 the rendering framebuffer might have changed since the frame callback
338 was called last. Call the functions sapp_width() and sapp_height()
339 each frame to get the current size.
340
341 --- Optionally implement the event-callback to handle input events.
342 sokol-app provides the following type of input events:
343 - a 'virtual key' was pressed down or released
344 - a single text character was entered (provided as UTF-32 code point)
345 - a mouse button was pressed down or released (left, right, middle)
346 - mouse-wheel or 2D scrolling events
347 - the mouse was moved
348 - the mouse has entered or left the application window boundaries
349 - low-level, portable multi-touch events (began, moved, ended, cancelled)
350 - the application window was resized, iconified or restored
351 - the application was suspended or restored (on mobile platforms)
352 - the user or application code has asked to quit the application
353 - a string was pasted to the system clipboard
354 - one or more files have been dropped onto the application window
355
356 To explicitly 'consume' an event and prevent that the event is
357 forwarded for further handling to the operating system, call
358 sapp_consume_event() from inside the event handler (NOTE that
359 this behaviour is currently only implemented for some HTML5
360 events, support for other platforms and event types will
361 be added as needed, please open a github ticket and/or provide
362 a PR if needed).
363
364 NOTE: Do *not* call any 3D API rendering functions in the event
365 callback function, since the 3D API context may not be active when the
366 event callback is called (it may work on some platforms and 3D APIs,
367 but not others, and the exact behaviour may change between
368 sokol-app versions).
369
370 --- Implement the cleanup-callback function, this is called once
371 after the user quits the application (see the section
372 "APPLICATION QUIT" for detailed information on quitting
373 behaviour, and how to intercept a pending quit - for instance to show a
374 "Really Quit?" dialog box). Note that the cleanup-callback isn't
375 guaranteed to be called on the web and mobile platforms.
376
377 MOUSE CURSOR TYPE AND VISIBILITY
378 ================================
379 You can show and hide the mouse cursor with
380
381 void sapp_show_mouse(bool show)
382
383 And to get the current shown status:
384
385 bool sapp_mouse_shown(void)
386
387 NOTE that hiding the mouse cursor is different and independent from
388 the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when
389 active (MOUSE LOCK is described below).
390
391 To change the mouse cursor to one of several predefined types, call
392 the function:
393
394 void sapp_set_mouse_cursor(sapp_mouse_cursor cursor)
395
396 Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore
397 the standard look.
398
399 To get the currently active mouse cursor type, call:
400
401 sapp_mouse_cursor sapp_get_mouse_cursor(void)
402
403 MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE)
404 ================================================
405 In normal mouse mode, no mouse movement events are reported when the
406 mouse leaves the windows client area or hits the screen border (whether
407 it's one or the other depends on the platform), and the mouse move events
408 (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in
409 framebuffer pixels in the sapp_event items mouse_x and mouse_y, and
410 relative movement in framebuffer pixels in the sapp_event items mouse_dx
411 and mouse_dy.
412
413 To get continuous mouse movement (also when the mouse leaves the window
414 client area or hits the screen border), activate mouse-lock mode
415 by calling:
416
417 sapp_lock_mouse(true)
418
419 When mouse lock is activated, the mouse pointer is hidden, the
420 reported absolute mouse position (sapp_event.mouse_x/y) appears
421 frozen, and the relative mouse movement in sapp_event.mouse_dx/dy
422 no longer has a direct relation to framebuffer pixels but instead
423 uses "raw mouse input" (what "raw mouse input" exactly means also
424 differs by platform).
425
426 To deactivate mouse lock and return to normal mouse mode, call
427
428 sapp_lock_mouse(false)
429
430 And finally, to check if mouse lock is currently active, call
431
432 if (sapp_mouse_locked()) { ... }
433
434 On native platforms, the sapp_lock_mouse() and sapp_mouse_locked()
435 functions work as expected (mouse lock is activated or deactivated
436 immediately when sapp_lock_mouse() is called, and sapp_mouse_locked()
437 also immediately returns the new state after sapp_lock_mouse()
438 is called.
439
440 On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave
441 differently, as dictated by the limitations of the HTML5 Pointer Lock API:
442
443 - sapp_lock_mouse(true) can be called at any time, but it will
444 only take effect in a 'short-lived input event handler of a specific
445 type', meaning when one of the following events happens:
446 - SAPP_EVENTTYPE_MOUSE_DOWN
447 - SAPP_EVENTTYPE_MOUSE_UP
448 - SAPP_EVENTTYPE_MOUSE_SCROLL
449 - SAPP_EVENTTYPE_KEY_UP
450 - SAPP_EVENTTYPE_KEY_DOWN
451 - The mouse lock/unlock action on the web platform is asynchronous,
452 this means that sapp_mouse_locked() won't immediately return
453 the new status after calling sapp_lock_mouse(), instead the
454 reported status will only change when the pointer lock has actually
455 been activated or deactivated in the browser.
456 - On the web, mouse lock can be deactivated by the user at any time
457 by pressing the Esc key. When this happens, sokol_app.h behaves
458 the same as if sapp_lock_mouse(false) is called.
459
460 For things like camera manipulation it's most straightforward to lock
461 and unlock the mouse right from the sokol_app.h event handler, for
462 instance the following code enters and leaves mouse lock when the
463 left mouse button is pressed and released, and then uses the relative
464 movement information to manipulate a camera (taken from the
465 cgltf-sapp.c sample in the sokol-samples repository
466 at https://github.com/floooh/sokol-samples):
467
468 static void input(const sapp_event* ev) {
469 switch (ev->type) {
470 case SAPP_EVENTTYPE_MOUSE_DOWN:
471 if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
472 sapp_lock_mouse(true);
473 }
474 break;
475
476 case SAPP_EVENTTYPE_MOUSE_UP:
477 if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
478 sapp_lock_mouse(false);
479 }
480 break;
481
482 case SAPP_EVENTTYPE_MOUSE_MOVE:
483 if (sapp_mouse_locked()) {
484 cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f);
485 }
486 break;
487
488 default:
489 break;
490 }
491 }
492
493 CLIPBOARD SUPPORT
494 =================
495 Applications can send and receive UTF-8 encoded text data from and to the
496 system clipboard. By default, clipboard support is disabled and
497 must be enabled at startup via the following sapp_desc struct
498 members:
499
500 sapp_desc.enable_clipboard - set to true to enable clipboard support
501 sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes
502
503 Enabling the clipboard will dynamically allocate a clipboard buffer
504 for UTF-8 encoded text data of the requested size in bytes, the default
505 size is 8 KBytes. Strings that don't fit into the clipboard buffer
506 (including the terminating zero) will be silently clipped, so it's
507 important that you provide a big enough clipboard size for your
508 use case.
509
510 To send data to the clipboard, call sapp_set_clipboard_string() with
511 a pointer to an UTF-8 encoded, null-terminated C-string.
512
513 NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be
514 called from inside a 'short-lived event handler', and there are a few
515 other HTML5-specific caveats to workaround. You'll basically have to
516 tinker until it works in all browsers :/ (maybe the situation will
517 improve when all browsers agree on and implement the new
518 HTML5 navigator.clipboard API).
519
520 To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED
521 event in your event handler function, and then call sapp_get_clipboard_string()
522 to obtain the pasted UTF-8 encoded text.
523
524 NOTE that behaviour of sapp_get_clipboard_string() is slightly different
525 depending on platform:
526
527 - on the HTML5 platform, the internal clipboard buffer will only be updated
528 right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent,
529 and sapp_get_clipboard_string() will simply return the current content
530 of the clipboard buffer
531 - on 'native' platforms, the call to sapp_get_clipboard_string() will
532 update the internal clipboard buffer with the most recent data
533 from the system clipboard
534
535 Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event,
536 and then call sapp_get_clipboard_string() right in the event handler.
537
538 The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app
539 as follows:
540
541 - on macOS: when the Cmd+V key is pressed down
542 - on HTML5: when the browser sends a 'paste' event to the global 'window' object
543 - on all other platforms: when the Ctrl+V key is pressed down
544
545 DRAG AND DROP SUPPORT
546 =====================
547 PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5
548 and on the native desktop platforms (Win32, Linux and macOS) because
549 of security-related restrictions in the HTML5 drag'n'drop API. The
550 WASM/HTML5 specifics are described at the end of this documentation
551 section:
552
553 Like clipboard support, drag'n'drop support must be explicitly enabled
554 at startup in the sapp_desc struct.
555
556 sapp_desc sokol_main() {
557 return (sapp_desc) {
558 .enable_dragndrop = true, // default is false
559 ...
560 };
561 }
562
563 You can also adjust the maximum number of files that are accepted
564 in a drop operation, and the maximum path length in bytes if needed:
565
566 sapp_desc sokol_main() {
567 return (sapp_desc) {
568 .enable_dragndrop = true, // default is false
569 .max_dropped_files = 8, // default is 1
570 .max_dropped_file_path_length = 8192, // in bytes, default is 2048
571 ...
572 };
573 }
574
575 When drag'n'drop is enabled, the event callback will be invoked with an
576 event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on
577 the application window.
578
579 After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the
580 number of dropped files, and their absolute paths by calling separate
581 functions:
582
583 void on_event(const sapp_event* ev) {
584 if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) {
585
586 // the mouse position where the drop happened
587 float x = ev->mouse_x;
588 float y = ev->mouse_y;
589
590 // get the number of files and their paths like this:
591 const int num_dropped_files = sapp_get_num_dropped_files();
592 for (int i = 0; i < num_dropped_files; i++) {
593 const char* path = sapp_get_dropped_file_path(i);
594 ...
595 }
596 }
597 }
598
599 The returned file paths are UTF-8 encoded strings.
600
601 You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path()
602 anywhere, also outside the event handler callback, but be aware that the
603 file path strings will be overwritten with the next drop operation.
604
605 In any case, sapp_get_dropped_file_path() will never return a null pointer,
606 instead an empty string "" will be returned if the drag'n'drop feature
607 hasn't been enabled, the last drop-operation failed, or the file path index
608 is out of range.
609
610 Drag'n'drop caveats:
611
612 - if more files are dropped in a single drop-action
613 than sapp_desc.max_dropped_files, the additional
614 files will be silently ignored
615 - if any of the file paths is longer than
616 sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8
617 encoding) the entire drop operation will be silently ignored (this
618 needs some sort of error feedback in the future)
619 - no mouse positions are reported while the drag is in
620 process, this may change in the future
621
622 Drag'n'drop on HTML5/WASM:
623
624 The HTML5 drag'n'drop API doesn't return file paths, but instead
625 black-box 'file objects' which must be used to load the content
626 of dropped files. This is the reason why sokol_app.h adds two
627 HTML5-specific functions to the drag'n'drop API:
628
629 uint32_t sapp_html5_get_dropped_file_size(int index)
630 Returns the size in bytes of a dropped file.
631
632 void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request)
633 Asynchronously loads the content of a dropped file into a
634 provided memory buffer (which must be big enough to hold
635 the file content)
636
637 To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED
638 event is received:
639
640 sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){
641 .dropped_file_index = 0,
642 .callback = fetch_cb
643 .buffer = {
644 .ptr = buf,
645 .size = sizeof(buf)
646 },
647 .user_data = ...
648 });
649
650 Make sure that the memory pointed to by 'buf' stays valid until the
651 callback function is called!
652
653 As result of the asynchronous loading operation (no matter if succeeded or
654 failed) the 'fetch_cb' function will be called:
655
656 void fetch_cb(const sapp_html5_fetch_response* response) {
657 // IMPORTANT: check if the loading operation actually succeeded:
658 if (response->succeeded) {
659 // the size of the loaded file:
660 const size_t num_bytes = response->data.size;
661 // and the pointer to the data (same as 'buf' in the fetch-call):
662 const void* ptr = response->data.ptr;
663 }
664 else {
665 // on error check the error code:
666 switch (response->error_code) {
667 case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL:
668 ...
669 break;
670 case SAPP_HTML5_FETCH_ERROR_OTHER:
671 ...
672 break;
673 }
674 }
675 }
676
677 Check the droptest-sapp example for a real-world example which works
678 both on native platforms and the web:
679
680 https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c
681
682 HIGH-DPI RENDERING
683 ==================
684 You can set the sapp_desc.high_dpi flag during initialization to request
685 a full-resolution framebuffer on HighDPI displays. The default behaviour
686 is sapp_desc.high_dpi=false, this means that the application will
687 render to a lower-resolution framebuffer on HighDPI displays and the
688 rendered content will be upscaled by the window system composer.
689
690 In a HighDPI scenario, you still request the same window size during
691 sokol_main(), but the framebuffer sizes returned by sapp_width()
692 and sapp_height() will be scaled up according to the DPI scaling
693 ratio.
694
695 Note that on some platforms the DPI scaling factor may change at any
696 time (for instance when a window is moved from a high-dpi display
697 to a low-dpi display).
698
699 To query the current DPI scaling factor, call the function:
700
701 float sapp_dpi_scale(void);
702
703 For instance on a Retina Mac, returning the following sapp_desc
704 struct from sokol_main():
705
706 sapp_desc sokol_main() {
707 return (sapp_desc) {
708 .width = 640,
709 .height = 480,
710 .high_dpi = true,
711 ...
712 };
713 }
714
715 ...the functions the functions sapp_width(), sapp_height()
716 and sapp_dpi_scale() will return the following values:
717
718 sapp_width: 1280
719 sapp_height: 960
720 sapp_dpi_scale: 2.0
721
722 If the high_dpi flag is false, or you're not running on a Retina display,
723 the values would be:
724
725 sapp_width: 640
726 sapp_height: 480
727 sapp_dpi_scale: 1.0
728
729 If the window is moved from the Retina display to a low-dpi external display,
730 the values would change as follows:
731
732 sapp_width: 1280 => 640
733 sapp_height: 960 => 480
734 sapp_dpi_scale: 2.0 => 1.0
735
736 Currently there is no event associated with a DPI change, but an
737 SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the
738 framebuffer size changing.
739
740 Per-monitor DPI is currently supported on macOS and Windows.
741
742 APPLICATION QUIT
743 ================
744 Without special quit handling, a sokol_app.h application will quit
745 'gracefully' when the user clicks the window close-button unless a
746 platform's application model prevents this (e.g. on web or mobile).
747 'Graceful exit' means that the application-provided cleanup callback will
748 be called before the application quits.
749
750 On native desktop platforms sokol_app.h provides more control over the
751 application-quit-process. It's possible to initiate a 'programmatic quit'
752 from the application code, and a quit initiated by the application user can
753 be intercepted (for instance to show a custom dialog box).
754
755 This 'programmatic quit protocol' is implemented through 3 functions
756 and 1 event:
757
758 - sapp_quit(): This function simply quits the application without
759 giving the user a chance to intervene. Usually this might
760 be called when the user clicks the 'Ok' button in a 'Really Quit?'
761 dialog box
762 - sapp_request_quit(): Calling sapp_request_quit() will send the
763 event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler
764 callback, giving the user code a chance to intervene and cancel the
765 pending quit process (for instance to show a 'Really Quit?' dialog
766 box). If the event handler callback does nothing, the application
767 will be quit as usual. To prevent this, call the function
768 sapp_cancel_quit() from inside the event handler.
769 - sapp_cancel_quit(): Cancels a pending quit request, either initiated
770 by the user clicking the window close button, or programmatically
771 by calling sapp_request_quit(). The only place where calling this
772 function makes sense is from inside the event handler callback when
773 the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received.
774 - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user
775 clicks the window's close button or application code calls the
776 sapp_request_quit() function. The event handler callback code can handle
777 this event by calling sapp_cancel_quit() to cancel the quit.
778 If the event is ignored, the application will quit as usual.
779
780 On the web platform, the quit behaviour differs from native platforms,
781 because of web-specific restrictions:
782
783 A `programmatic quit` initiated by calling sapp_quit() or
784 sapp_request_quit() will work as described above: the cleanup callback is
785 called, platform-specific cleanup is performed (on the web
786 this means that JS event handlers are unregisters), and then
787 the request-animation-loop will be exited. However that's all. The
788 web page itself will continue to exist (e.g. it's not possible to
789 programmatically close the browser tab).
790
791 On the web it's also not possible to run custom code when the user
792 closes a brower tab, so it's not possible to prevent this with a
793 fancy custom dialog box.
794
795 Instead the standard "Leave Site?" dialog box can be activated (or
796 deactivated) with the following function:
797
798 sapp_html5_ask_leave_site(bool ask);
799
800 The initial state of the associated internal flag can be provided
801 at startup via sapp_desc.html5_ask_leave_site.
802
803 This feature should only be used sparingly in critical situations - for
804 instance when the user would loose data - since popping up modal dialog
805 boxes is considered quite rude in the web world. Note that there's no way
806 to customize the content of this dialog box or run any code as a result
807 of the user's decision. Also note that the user must have interacted with
808 the site before the dialog box will appear. These are all security measures
809 to prevent fishing.
810
811 The Dear ImGui HighDPI sample contains example code of how to
812 implement a 'Really Quit?' dialog box with Dear ImGui (native desktop
813 platforms only), and for showing the hardwired "Leave Site?" dialog box
814 when running on the web platform:
815
816 https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html
817
818 FULLSCREEN
819 ==========
820 If the sapp_desc.fullscreen flag is true, sokol-app will try to create
821 a fullscreen window on platforms with a 'proper' window system
822 (mobile devices will always use fullscreen). The implementation details
823 depend on the target platform, in general sokol-app will use a
824 'soft approach' which doesn't interfere too much with the platform's
825 window system (for instance borderless fullscreen window instead of
826 a 'real' fullscreen mode). Such details might change over time
827 as sokol-app is adapted for different needs.
828
829 The most important effect of fullscreen mode to keep in mind is that
830 the requested canvas width and height will be ignored for the initial
831 window size, calling sapp_width() and sapp_height() will instead return
832 the resolution of the fullscreen canvas (however the provided size
833 might still be used for the non-fullscreen window, in case the user can
834 switch back from fullscreen- to windowed-mode).
835
836 To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen().
837
838 To check if the application window is currently in fullscreen mode,
839 call sapp_is_fullscreen().
840
841 WINDOW ICON SUPPORT
842 ===================
843 Some sokol_app.h backends allow to change the window icon programmatically:
844
845 - on Win32: the small icon in the window's title bar, and the
846 bigger icon in the task bar
847 - on Linux: highly dependent on the used window manager, but usually
848 the window's title bar icon and/or the task bar icon
849 - on HTML5: the favicon shown in the page's browser tab
850
851 NOTE that it is not possible to set the actual application icon which is
852 displayed by the operating system on the desktop or 'home screen'. Those
853 icons must be provided 'traditionally' through operating-system-specific
854 resources which are associated with the application (sokol_app.h might
855 later support setting the window icon from platform specific resource data
856 though).
857
858 There are two ways to set the window icon:
859
860 - at application start in the sokol_main() function by initializing
861 the sapp_desc.icon nested struct
862 - or later by calling the function sapp_set_icon()
863
864 As a convenient shortcut, sokol_app.h comes with a builtin default-icon
865 (a rainbow-colored 'S', which at least looks a bit better than the Windows
866 default icon for applications), which can be activated like this:
867
868 At startup in sokol_main():
869
870 sapp_desc sokol_main(...) {
871 return (sapp_desc){
872 ...
873 icon.sokol_default = true
874 };
875 }
876
877 Or later by calling:
878
879 sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true });
880
881 NOTE that a completely zero-initialized sapp_icon_desc struct will not
882 update the window icon in any way. This is an 'escape hatch' so that you
883 can handle the window icon update yourself (or if you do this already,
884 sokol_app.h won't get in your way, in this case just leave the
885 sapp_desc.icon struct zero-initialized).
886
887 Providing your own icon images works exactly like in GLFW (down to the
888 data format):
889
890 You provide one or more 'candidate images' in different sizes, and the
891 sokol_app.h platform backends pick the best match for the specific backend
892 and icon type.
893
894 For each candidate image, you need to provide:
895
896 - the width in pixels
897 - the height in pixels
898 - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844
899 on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44)
900
901 For instance, if you have 3 candidate images (small, medium, big) of
902 sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup
903 like this:
904
905 // the actual pixel data (RGBA8, origin top-left)
906 const uint32_t small[16][16] = { ... };
907 const uint32_t medium[32][32] = { ... };
908 const uint32_t big[64][64] = { ... };
909
910 const sapp_icon_desc icon_desc = {
911 .images = {
912 { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) },
913 { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) },
914 // ...or without the SAPP_RANGE helper macro:
915 { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } }
916 }
917 };
918
919 An sapp_icon_desc struct initialized like this can then either be applied
920 at application start in sokol_main:
921
922 sapp_desc sokol_main(...) {
923 return (sapp_desc){
924 ...
925 icon = icon_desc
926 };
927 }
928
929 ...or later by calling sapp_set_icon():
930
931 sapp_set_icon(&icon_desc);
932
933 Some window icon caveats:
934
935 - once the window icon has been updated, there's no way to go back to
936 the platform's default icon, this is because some platforms (Linux
937 and HTML5) don't switch the icon visual back to the default even if
938 the custom icon is deleted or removed
939 - on HTML5, if the sokol_app.h icon doesn't show up in the browser
940 tab, check that there's no traditional favicon 'link' element
941 is defined in the page's index.html, sokol_app.h will only
942 append a new favicon link element, but not delete any manually
943 defined favicon in the page
944
945 For an example and test of the window icon feature, check out the the
946 'icon-sapp' sample on the sokol-samples git repository.
947
948 ONSCREEN KEYBOARD
949 =================
950 On some platforms which don't provide a physical keyboard, sokol-app
951 can display the platform's integrated onscreen keyboard for text
952 input. To request that the onscreen keyboard is shown, call
953
954 sapp_show_keyboard(true);
955
956 Likewise, to hide the keyboard call:
957
958 sapp_show_keyboard(false);
959
960 Note that on the web platform, the keyboard can only be shown from
961 inside an input handler. On such platforms, sapp_show_keyboard()
962 will only work as expected when it is called from inside the
963 sokol-app event callback function. When called from other places,
964 an internal flag will be set, and the onscreen keyboard will be
965 called at the next 'legal' opportunity (when the next input event
966 is handled).
967
968 OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY)
969 ======================================================
970 In its default configuration, sokol_app.h "hijacks" the platform's
971 standard main() function. This was done because different platforms
972 have different main functions which are not compatible with
973 C's main() (for instance WinMain on Windows has completely different
974 arguments). However, this "main hijacking" posed a problem for
975 usage scenarios like integrating sokol_app.h with other languages than
976 C or C++, so an alternative SOKOL_NO_ENTRY mode has been added
977 in which the user code provides the platform's main function:
978
979 - define SOKOL_NO_ENTRY before including the sokol_app.h implementation
980 - do *not* provide a sokol_main() function
981 - instead provide the standard main() function of the platform
982 - from the main function, call the function ```sapp_run()``` which
983 takes a pointer to an ```sapp_desc``` structure.
984 - ```sapp_run()``` takes over control and calls the provided init-, frame-,
985 shutdown- and event-callbacks just like in the default model, it
986 will only return when the application quits (or not at all on some
987 platforms, like emscripten)
988
989 NOTE: SOKOL_NO_ENTRY is currently not supported on Android.
990
991 WINDOWS CONSOLE OUTPUT
992 ======================
993 On Windows, regular windowed applications don't show any stdout/stderr text
994 output, which can be a bit of a hassle for printf() debugging or generally
995 logging text to the console. Also, console output by default uses a local
996 codepage setting and thus international UTF-8 encoded text is printed
997 as garbage.
998
999 To help with these issues, sokol_app.h can be configured at startup
1000 via the following Windows-specific sapp_desc flags:
1001
1002 sapp_desc.win32_console_utf8 (default: false)
1003 When set to true, the output console codepage will be switched
1004 to UTF-8 (and restored to the original codepage on exit)
1005
1006 sapp_desc.win32_console_attach (default: false)
1007 When set to true, stdout and stderr will be attached to the
1008 console of the parent process (if the parent process actually
1009 has a console). This means that if the application was started
1010 in a command line window, stdout and stderr output will be printed
1011 to the terminal, just like a regular command line program. But if
1012 the application is started via double-click, it will behave like
1013 a regular UI application, and stdout/stderr will not be visible.
1014
1015 sapp_desc.win32_console_create (default: false)
1016 When set to true, a new console window will be created and
1017 stdout/stderr will be redirected to that console window. It
1018 doesn't matter if the application is started from the command
1019 line or via double-click.
1020
1021 MEMORY ALLOCATION OVERRIDE
1022 ==========================
1023 You can override the memory allocation functions at initialization time
1024 like this:
1025
1026 void* my_alloc(size_t size, void* user_data) {
1027 return malloc(size);
1028 }
1029
1030 void my_free(void* ptr, void* user_data) {
1031 free(ptr);
1032 }
1033
1034 sapp_desc sokol_main(int argc, char* argv[]) {
1035 return (sapp_desc){
1036 // ...
1037 .allocator = {
1038 .alloc = my_alloc,
1039 .free = my_free,
1040 .user_data = ...,
1041 }
1042 };
1043 }
1044
1045 If no overrides are provided, malloc and free will be used.
1046
1047 This only affects memory allocation calls done by sokol_app.h
1048 itself though, not any allocations in OS libraries.
1049
1050
1051 LOG FUNCTION OVERRIDE
1052 =====================
1053 You can override the log function at initialization time like this:
1054
1055 void my_log(const char* message, void* user_data) {
1056 printf("sapp says: \s\n", message);
1057 }
1058
1059 sapp_desc sokol_main(int argc, char* argv[]) {
1060 return (sapp_desc){
1061 // ...
1062 .logger = {
1063 .log_cb = my_log,
1064 .user_data = ...,
1065 }
1066 };
1067 }
1068
1069 If no overrides are provided, puts will be used on most platforms.
1070 On Android, __android_log_write will be used instead.
1071
1072
1073 TEMP NOTE DUMP
1074 ==============
1075 - onscreen keyboard support on Android requires Java :(, should we even bother?
1076 - sapp_desc needs a bool whether to initialize depth-stencil surface
1077 - GL context initialization needs more control (at least what GL version to initialize)
1078 - application icon
1079 - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy
1080 at the latest but should do it earlier, in onStop, as an app is "killable" after onStop
1081 on Android Honeycomb and later (it can't be done at the moment as the app may be started
1082 again after onStop and the sokol lifecycle does not yet handle context teardown/bringup)
1083
1084
1085 LICENSE
1086 =======
1087 zlib/libpng license
1088
1089 Copyright (c) 2018 Andre Weissflog
1090
1091 This software is provided 'as-is', without any express or implied warranty.
1092 In no event will the authors be held liable for any damages arising from the
1093 use of this software.
1094
1095 Permission is granted to anyone to use this software for any purpose,
1096 including commercial applications, and to alter it and redistribute it
1097 freely, subject to the following restrictions:
1098
1099 1. The origin of this software must not be misrepresented; you must not
1100 claim that you wrote the original software. If you use this software in a
1101 product, an acknowledgment in the product documentation would be
1102 appreciated but is not required.
1103
1104 2. Altered source versions must be plainly marked as such, and must not
1105 be misrepresented as being the original software.
1106
1107 3. This notice may not be removed or altered from any source
1108 distribution.
1109*/
1110#define SOKOL_APP_INCLUDED (1)
1111#include <stddef.h> // size_t
1112#include <stdint.h>
1113#include <stdbool.h>
1114
1115#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL)
1116#define SOKOL_APP_API_DECL SOKOL_API_DECL
1117#endif
1118#ifndef SOKOL_APP_API_DECL
1119#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL)
1120#define SOKOL_APP_API_DECL __declspec(dllexport)
1121#elif defined(_WIN32) && defined(SOKOL_DLL)
1122#define SOKOL_APP_API_DECL __declspec(dllimport)
1123#else
1124#define SOKOL_APP_API_DECL extern
1125#endif
1126#endif
1127
1128#ifdef __cplusplus
1129extern "C" {
1130#endif
1131
1132/* misc constants */
1133enum {
1134 SAPP_MAX_TOUCHPOINTS = 8,
1135 SAPP_MAX_MOUSEBUTTONS = 3,
1136 SAPP_MAX_KEYCODES = 512,
1137 SAPP_MAX_ICONIMAGES = 8,
1138};
1139
1140/*
1141 sapp_event_type
1142
1143 The type of event that's passed to the event handler callback
1144 in the sapp_event.type field. These are not just "traditional"
1145 input events, but also notify the application about state changes
1146 or other user-invoked actions.
1147*/
1148typedef enum sapp_event_type {
1149 SAPP_EVENTTYPE_INVALID,
1150 SAPP_EVENTTYPE_KEY_DOWN,
1151 SAPP_EVENTTYPE_KEY_UP,
1152 SAPP_EVENTTYPE_CHAR,
1153 SAPP_EVENTTYPE_MOUSE_DOWN,
1154 SAPP_EVENTTYPE_MOUSE_UP,
1155 SAPP_EVENTTYPE_MOUSE_SCROLL,
1156 SAPP_EVENTTYPE_MOUSE_MOVE,
1157 SAPP_EVENTTYPE_MOUSE_ENTER,
1158 SAPP_EVENTTYPE_MOUSE_LEAVE,
1159 SAPP_EVENTTYPE_TOUCHES_BEGAN,
1160 SAPP_EVENTTYPE_TOUCHES_MOVED,
1161 SAPP_EVENTTYPE_TOUCHES_ENDED,
1162 SAPP_EVENTTYPE_TOUCHES_CANCELLED,
1163 SAPP_EVENTTYPE_RESIZED,
1164 SAPP_EVENTTYPE_ICONIFIED,
1165 SAPP_EVENTTYPE_RESTORED,
1166 SAPP_EVENTTYPE_FOCUSED,
1167 SAPP_EVENTTYPE_UNFOCUSED,
1168 SAPP_EVENTTYPE_SUSPENDED,
1169 SAPP_EVENTTYPE_RESUMED,
1170 SAPP_EVENTTYPE_QUIT_REQUESTED,
1171 SAPP_EVENTTYPE_CLIPBOARD_PASTED,
1172 SAPP_EVENTTYPE_FILES_DROPPED,
1173 _SAPP_EVENTTYPE_NUM,
1174 _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF
1175} sapp_event_type;
1176
1177/*
1178 sapp_keycode
1179
1180 The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the
1181 struct field sapp_event.key_code.
1182
1183 Note that the keycode values are identical with GLFW.
1184*/
1185typedef enum sapp_keycode {
1186 SAPP_KEYCODE_INVALID = 0,
1187 SAPP_KEYCODE_SPACE = 32,
1188 SAPP_KEYCODE_APOSTROPHE = 39, /* ' */
1189 SAPP_KEYCODE_COMMA = 44, /* , */
1190 SAPP_KEYCODE_MINUS = 45, /* - */
1191 SAPP_KEYCODE_PERIOD = 46, /* . */
1192 SAPP_KEYCODE_SLASH = 47, /* / */
1193 SAPP_KEYCODE_0 = 48,
1194 SAPP_KEYCODE_1 = 49,
1195 SAPP_KEYCODE_2 = 50,
1196 SAPP_KEYCODE_3 = 51,
1197 SAPP_KEYCODE_4 = 52,
1198 SAPP_KEYCODE_5 = 53,
1199 SAPP_KEYCODE_6 = 54,
1200 SAPP_KEYCODE_7 = 55,
1201 SAPP_KEYCODE_8 = 56,
1202 SAPP_KEYCODE_9 = 57,
1203 SAPP_KEYCODE_SEMICOLON = 59, /* ; */
1204 SAPP_KEYCODE_EQUAL = 61, /* = */
1205 SAPP_KEYCODE_A = 65,
1206 SAPP_KEYCODE_B = 66,
1207 SAPP_KEYCODE_C = 67,
1208 SAPP_KEYCODE_D = 68,
1209 SAPP_KEYCODE_E = 69,
1210 SAPP_KEYCODE_F = 70,
1211 SAPP_KEYCODE_G = 71,
1212 SAPP_KEYCODE_H = 72,
1213 SAPP_KEYCODE_I = 73,
1214 SAPP_KEYCODE_J = 74,
1215 SAPP_KEYCODE_K = 75,
1216 SAPP_KEYCODE_L = 76,
1217 SAPP_KEYCODE_M = 77,
1218 SAPP_KEYCODE_N = 78,
1219 SAPP_KEYCODE_O = 79,
1220 SAPP_KEYCODE_P = 80,
1221 SAPP_KEYCODE_Q = 81,
1222 SAPP_KEYCODE_R = 82,
1223 SAPP_KEYCODE_S = 83,
1224 SAPP_KEYCODE_T = 84,
1225 SAPP_KEYCODE_U = 85,
1226 SAPP_KEYCODE_V = 86,
1227 SAPP_KEYCODE_W = 87,
1228 SAPP_KEYCODE_X = 88,
1229 SAPP_KEYCODE_Y = 89,
1230 SAPP_KEYCODE_Z = 90,
1231 SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */
1232 SAPP_KEYCODE_BACKSLASH = 92, /* \ */
1233 SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */
1234 SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */
1235 SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */
1236 SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */
1237 SAPP_KEYCODE_ESCAPE = 256,
1238 SAPP_KEYCODE_ENTER = 257,
1239 SAPP_KEYCODE_TAB = 258,
1240 SAPP_KEYCODE_BACKSPACE = 259,
1241 SAPP_KEYCODE_INSERT = 260,
1242 SAPP_KEYCODE_DELETE = 261,
1243 SAPP_KEYCODE_RIGHT = 262,
1244 SAPP_KEYCODE_LEFT = 263,
1245 SAPP_KEYCODE_DOWN = 264,
1246 SAPP_KEYCODE_UP = 265,
1247 SAPP_KEYCODE_PAGE_UP = 266,
1248 SAPP_KEYCODE_PAGE_DOWN = 267,
1249 SAPP_KEYCODE_HOME = 268,
1250 SAPP_KEYCODE_END = 269,
1251 SAPP_KEYCODE_CAPS_LOCK = 280,
1252 SAPP_KEYCODE_SCROLL_LOCK = 281,
1253 SAPP_KEYCODE_NUM_LOCK = 282,
1254 SAPP_KEYCODE_PRINT_SCREEN = 283,
1255 SAPP_KEYCODE_PAUSE = 284,
1256 SAPP_KEYCODE_F1 = 290,
1257 SAPP_KEYCODE_F2 = 291,
1258 SAPP_KEYCODE_F3 = 292,
1259 SAPP_KEYCODE_F4 = 293,
1260 SAPP_KEYCODE_F5 = 294,
1261 SAPP_KEYCODE_F6 = 295,
1262 SAPP_KEYCODE_F7 = 296,
1263 SAPP_KEYCODE_F8 = 297,
1264 SAPP_KEYCODE_F9 = 298,
1265 SAPP_KEYCODE_F10 = 299,
1266 SAPP_KEYCODE_F11 = 300,
1267 SAPP_KEYCODE_F12 = 301,
1268 SAPP_KEYCODE_F13 = 302,
1269 SAPP_KEYCODE_F14 = 303,
1270 SAPP_KEYCODE_F15 = 304,
1271 SAPP_KEYCODE_F16 = 305,
1272 SAPP_KEYCODE_F17 = 306,
1273 SAPP_KEYCODE_F18 = 307,
1274 SAPP_KEYCODE_F19 = 308,
1275 SAPP_KEYCODE_F20 = 309,
1276 SAPP_KEYCODE_F21 = 310,
1277 SAPP_KEYCODE_F22 = 311,
1278 SAPP_KEYCODE_F23 = 312,
1279 SAPP_KEYCODE_F24 = 313,
1280 SAPP_KEYCODE_F25 = 314,
1281 SAPP_KEYCODE_KP_0 = 320,
1282 SAPP_KEYCODE_KP_1 = 321,
1283 SAPP_KEYCODE_KP_2 = 322,
1284 SAPP_KEYCODE_KP_3 = 323,
1285 SAPP_KEYCODE_KP_4 = 324,
1286 SAPP_KEYCODE_KP_5 = 325,
1287 SAPP_KEYCODE_KP_6 = 326,
1288 SAPP_KEYCODE_KP_7 = 327,
1289 SAPP_KEYCODE_KP_8 = 328,
1290 SAPP_KEYCODE_KP_9 = 329,
1291 SAPP_KEYCODE_KP_DECIMAL = 330,
1292 SAPP_KEYCODE_KP_DIVIDE = 331,
1293 SAPP_KEYCODE_KP_MULTIPLY = 332,
1294 SAPP_KEYCODE_KP_SUBTRACT = 333,
1295 SAPP_KEYCODE_KP_ADD = 334,
1296 SAPP_KEYCODE_KP_ENTER = 335,
1297 SAPP_KEYCODE_KP_EQUAL = 336,
1298 SAPP_KEYCODE_LEFT_SHIFT = 340,
1299 SAPP_KEYCODE_LEFT_CONTROL = 341,
1300 SAPP_KEYCODE_LEFT_ALT = 342,
1301 SAPP_KEYCODE_LEFT_SUPER = 343,
1302 SAPP_KEYCODE_RIGHT_SHIFT = 344,
1303 SAPP_KEYCODE_RIGHT_CONTROL = 345,
1304 SAPP_KEYCODE_RIGHT_ALT = 346,
1305 SAPP_KEYCODE_RIGHT_SUPER = 347,
1306 SAPP_KEYCODE_MENU = 348,
1307} sapp_keycode;
1308
1309/*
1310 Android specific 'tool type' enum for touch events. This lets the
1311 application check what type of input device was used for
1312 touch events.
1313
1314 NOTE: the values must remain in sync with the corresponding
1315 Android SDK type, so don't change those.
1316
1317 See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN
1318*/
1319typedef enum sapp_android_tooltype {
1320 SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN
1321 SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER
1322 SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS
1323 SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE
1324} sapp_android_tooltype;
1325
1326/*
1327 sapp_touchpoint
1328
1329 Describes a single touchpoint in a multitouch event (TOUCHES_BEGAN,
1330 TOUCHES_MOVED, TOUCHES_ENDED).
1331
1332 Touch points are stored in the nested array sapp_event.touches[],
1333 and the number of touches is stored in sapp_event.num_touches.
1334*/
1335typedef struct sapp_touchpoint {
1336 uintptr_t identifier;
1337 float pos_x;
1338 float pos_y;
1339 sapp_android_tooltype android_tooltype; // only valid on Android
1340 bool changed;
1341} sapp_touchpoint;
1342
1343/*
1344 sapp_mousebutton
1345
1346 The currently pressed mouse button in the events MOUSE_DOWN
1347 and MOUSE_UP, stored in the struct field sapp_event.mouse_button.
1348*/
1349typedef enum sapp_mousebutton {
1350 SAPP_MOUSEBUTTON_LEFT = 0x0,
1351 SAPP_MOUSEBUTTON_RIGHT = 0x1,
1352 SAPP_MOUSEBUTTON_MIDDLE = 0x2,
1353 SAPP_MOUSEBUTTON_INVALID = 0x100,
1354} sapp_mousebutton;
1355
1356/*
1357 These are currently pressed modifier keys (and mouse buttons) which are
1358 passed in the event struct field sapp_event.modifiers.
1359*/
1360enum {
1361 SAPP_MODIFIER_SHIFT = 0x1, // left or right shift key
1362 SAPP_MODIFIER_CTRL = 0x2, // left or right control key
1363 SAPP_MODIFIER_ALT = 0x4, // left or right alt key
1364 SAPP_MODIFIER_SUPER = 0x8, // left or right 'super' key
1365 SAPP_MODIFIER_LMB = 0x100, // left mouse button
1366 SAPP_MODIFIER_RMB = 0x200, // right mouse button
1367 SAPP_MODIFIER_MMB = 0x400, // middle mouse button
1368};
1369
1370/*
1371 sapp_event
1372
1373 This is an all-in-one event struct passed to the event handler
1374 user callback function. Note that it depends on the event
1375 type what struct fields actually contain useful values, so you
1376 should first check the event type before reading other struct
1377 fields.
1378*/
1379typedef struct sapp_event {
1380 uint64_t frame_count; // current frame counter, always valid, useful for checking if two events were issued in the same frame
1381 sapp_event_type type; // the event type, always valid
1382 sapp_keycode key_code; // the virtual key code, only valid in KEY_UP, KEY_DOWN
1383 uint32_t char_code; // the UTF-32 character code, only valid in CHAR events
1384 bool key_repeat; // true if this is a key-repeat event, valid in KEY_UP, KEY_DOWN and CHAR
1385 uint32_t modifiers; // current modifier keys, valid in all key-, char- and mouse-events
1386 sapp_mousebutton mouse_button; // mouse button that was pressed or released, valid in MOUSE_DOWN, MOUSE_UP
1387 float mouse_x; // current horizontal mouse position in pixels, always valid except during mouse lock
1388 float mouse_y; // current vertical mouse position in pixels, always valid except during mouse lock
1389 float mouse_dx; // relative horizontal mouse movement since last frame, always valid
1390 float mouse_dy; // relative vertical mouse movement since last frame, always valid
1391 float scroll_x; // horizontal mouse wheel scroll distance, valid in MOUSE_SCROLL events
1392 float scroll_y; // vertical mouse wheel scroll distance, valid in MOUSE_SCROLL events
1393 int num_touches; // number of valid items in the touches[] array
1394 sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; // current touch points, valid in TOUCHES_BEGIN, TOUCHES_MOVED, TOUCHES_ENDED
1395 int window_width; // current window- and framebuffer sizes in pixels, always valid
1396 int window_height;
1397 int framebuffer_width; // = window_width * dpi_scale
1398 int framebuffer_height; // = window_height * dpi_scale
1399} sapp_event;
1400
1401/*
1402 sg_range
1403
1404 A general pointer/size-pair struct and constructor macros for passing binary blobs
1405 into sokol_app.h.
1406*/
1407typedef struct sapp_range {
1408 const void* ptr;
1409 size_t size;
1410} sapp_range;
1411// disabling this for every includer isn't great, but the warnings are also quite pointless
1412#if defined(_MSC_VER)
1413#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */
1414#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */
1415#endif
1416#if defined(__cplusplus)
1417#define SAPP_RANGE(x) sapp_range{ &x, sizeof(x) }
1418#else
1419#define SAPP_RANGE(x) (sapp_range){ &x, sizeof(x) }
1420#endif
1421
1422/*
1423 sapp_image_desc
1424
1425 This is used to describe image data to sokol_app.h (at first, window
1426 icons, later maybe cursor images).
1427
1428 Note that the actual image pixel format depends on the use case:
1429
1430 - window icon pixels are RGBA8
1431 - cursor images are ??? (FIXME)
1432*/
1433typedef struct sapp_image_desc {
1434 int width;
1435 int height;
1436 sapp_range pixels;
1437} sapp_image_desc;
1438
1439/*
1440 sapp_icon_desc
1441
1442 An icon description structure for use in sapp_desc.icon and
1443 sapp_set_icon().
1444
1445 When setting a custom image, the application can provide a number of
1446 candidates differing in size, and sokol_app.h will pick the image(s)
1447 closest to the size expected by the platform's window system.
1448
1449 To set sokol-app's default icon, set .sokol_default to true.
1450
1451 Otherwise provide candidate images of different sizes in the
1452 images[] array.
1453
1454 If both the sokol_default flag is set to true, any image candidates
1455 will be ignored and the sokol_app.h default icon will be set.
1456*/
1457typedef struct sapp_icon_desc {
1458 bool sokol_default;
1459 sapp_image_desc images[SAPP_MAX_ICONIMAGES];
1460} sapp_icon_desc;
1461
1462/*
1463 sapp_allocator
1464
1465 Used in sapp_desc to provide custom memory-alloc and -free functions
1466 to sokol_app.h. If memory management should be overridden, both the
1467 alloc and free function must be provided (e.g. it's not valid to
1468 override one function but not the other).
1469*/
1470typedef struct sapp_allocator {
1471 void* (*alloc)(size_t size, void* user_data);
1472 void (*free)(void* ptr, void* user_data);
1473 void* user_data;
1474} sapp_allocator;
1475
1476/*
1477 sapp_logger
1478
1479 Used in sapp_desc to provide custom log callbacks to sokol_app.h.
1480 Default behavior is SOKOL_LOG(message).
1481*/
1482typedef struct sapp_logger {
1483 void (*log_cb)(const char* message, void* user_data);
1484 void* user_data;
1485} sapp_logger;
1486
1487typedef struct sapp_desc {
1488 void (*init_cb)(void); // these are the user-provided callbacks without user data
1489 void (*frame_cb)(void);
1490 void (*cleanup_cb)(void);
1491 void (*event_cb)(const sapp_event*);
1492 void (*fail_cb)(const char*);
1493
1494 void* user_data; // these are the user-provided callbacks with user data
1495 void (*init_userdata_cb)(void*);
1496 void (*frame_userdata_cb)(void*);
1497 void (*cleanup_userdata_cb)(void*);
1498 void (*event_userdata_cb)(const sapp_event*, void*);
1499 void (*fail_userdata_cb)(const char*, void*);
1500
1501 int width; // the preferred width of the window / canvas
1502 int height; // the preferred height of the window / canvas
1503 int sample_count; // MSAA sample count
1504 int swap_interval; // the preferred swap interval (ignored on some platforms)
1505 bool high_dpi; // whether the rendering canvas is full-resolution on HighDPI displays
1506 bool fullscreen; // whether the window should be created in fullscreen mode
1507 bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms)
1508 const char* window_title; // the window title as UTF-8 encoded string
1509 bool enable_clipboard; // enable clipboard access, default is false
1510 int clipboard_size; // max size of clipboard content in bytes
1511 bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false
1512 int max_dropped_files; // max number of dropped files to process (default: 1)
1513 int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048)
1514 sapp_icon_desc icon; // the initial window icon to set
1515 sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free)
1516 sapp_logger logger; // optional log callback overrides (default: SAPP_LOG(message))
1517
1518 /* backend-specific options */
1519 bool gl_force_gles2; // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available
1520 int gl_major_version; // override GL major and minor version (the default GL version is 3.2)
1521 int gl_minor_version;
1522 bool win32_console_utf8; // if true, set the output console codepage to UTF-8
1523 bool win32_console_create; // if true, attach stdout/stderr to a new console window
1524 bool win32_console_attach; // if true, attach stdout/stderr to parent process
1525 const char* html5_canvas_name; // the name (id) of the HTML5 canvas element, default is "canvas"
1526 bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked
1527 bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames
1528 bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention
1529 bool html5_ask_leave_site; // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site())
1530 bool ios_keyboard_resizes_canvas; // if true, showing the iOS keyboard shrinks the canvas
1531 // __v_ start
1532 bool __v_native_render; // V patch to allow for native rendering
1533 // __v_ end
1534} sapp_desc;
1535
1536/* HTML5 specific: request and response structs for
1537 asynchronously loading dropped-file content.
1538*/
1539typedef enum sapp_html5_fetch_error {
1540 SAPP_HTML5_FETCH_ERROR_NO_ERROR,
1541 SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL,
1542 SAPP_HTML5_FETCH_ERROR_OTHER,
1543} sapp_html5_fetch_error;
1544
1545typedef struct sapp_html5_fetch_response {
1546 bool succeeded; // true if the loading operation has succeeded
1547 sapp_html5_fetch_error error_code;
1548 int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1)
1549 sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size)
1550 sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size)
1551 void* user_data; // user-provided user data pointer
1552} sapp_html5_fetch_response;
1553
1554typedef struct sapp_html5_fetch_request {
1555 int dropped_file_index; // 0..sapp_get_num_dropped_files()-1
1556 void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required)
1557 sapp_range buffer; // ptr/size of a memory buffer to load the data into
1558 void* user_data; // optional userdata pointer
1559} sapp_html5_fetch_request;
1560
1561/*
1562 sapp_mouse_cursor
1563
1564 Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor)
1565*/
1566typedef enum sapp_mouse_cursor {
1567 SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor
1568 SAPP_MOUSECURSOR_ARROW,
1569 SAPP_MOUSECURSOR_IBEAM,
1570 SAPP_MOUSECURSOR_CROSSHAIR,
1571 SAPP_MOUSECURSOR_POINTING_HAND,
1572 SAPP_MOUSECURSOR_RESIZE_EW,
1573 SAPP_MOUSECURSOR_RESIZE_NS,
1574 SAPP_MOUSECURSOR_RESIZE_NWSE,
1575 SAPP_MOUSECURSOR_RESIZE_NESW,
1576 SAPP_MOUSECURSOR_RESIZE_ALL,
1577 SAPP_MOUSECURSOR_NOT_ALLOWED,
1578 _SAPP_MOUSECURSOR_NUM,
1579} sapp_mouse_cursor;
1580
1581/* user-provided functions */
1582extern sapp_desc sokol_main(int argc, char* argv[]);
1583
1584/* returns true after sokol-app has been initialized */
1585SOKOL_APP_API_DECL bool sapp_isvalid(void);
1586/* returns the current framebuffer width in pixels */
1587SOKOL_APP_API_DECL int sapp_width(void);
1588/* same as sapp_width(), but returns float */
1589SOKOL_APP_API_DECL float sapp_widthf(void);
1590/* returns the current framebuffer height in pixels */
1591SOKOL_APP_API_DECL int sapp_height(void);
1592/* same as sapp_height(), but returns float */
1593SOKOL_APP_API_DECL float sapp_heightf(void);
1594/* get default framebuffer color pixel format */
1595SOKOL_APP_API_DECL int sapp_color_format(void);
1596/* get default framebuffer depth pixel format */
1597SOKOL_APP_API_DECL int sapp_depth_format(void);
1598/* get default framebuffer sample count */
1599SOKOL_APP_API_DECL int sapp_sample_count(void);
1600/* returns true when high_dpi was requested and actually running in a high-dpi scenario */
1601SOKOL_APP_API_DECL bool sapp_high_dpi(void);
1602/* returns the dpi scaling factor (window pixels to framebuffer pixels) */
1603SOKOL_APP_API_DECL float sapp_dpi_scale(void);
1604/* show or hide the mobile device onscreen keyboard */
1605SOKOL_APP_API_DECL void sapp_show_keyboard(bool show);
1606/* return true if the mobile device onscreen keyboard is currently shown */
1607SOKOL_APP_API_DECL bool sapp_keyboard_shown(void);
1608/* query fullscreen mode */
1609SOKOL_APP_API_DECL bool sapp_is_fullscreen(void);
1610/* toggle fullscreen mode */
1611SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void);
1612/* show or hide the mouse cursor */
1613SOKOL_APP_API_DECL void sapp_show_mouse(bool show);
1614/* show or hide the mouse cursor */
1615SOKOL_APP_API_DECL bool sapp_mouse_shown(void);
1616/* enable/disable mouse-pointer-lock mode */
1617SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock);
1618/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */
1619SOKOL_APP_API_DECL bool sapp_mouse_locked(void);
1620/* set mouse cursor type */
1621SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor);
1622/* get current mouse cursor type */
1623SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void);
1624/* return the userdata pointer optionally provided in sapp_desc */
1625SOKOL_APP_API_DECL void* sapp_userdata(void);
1626/* return a copy of the sapp_desc structure */
1627SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void);
1628/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */
1629SOKOL_APP_API_DECL void sapp_request_quit(void);
1630/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */
1631SOKOL_APP_API_DECL void sapp_cancel_quit(void);
1632/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */
1633SOKOL_APP_API_DECL void sapp_quit(void);
1634/* call from inside event callback to consume the current event (don't forward to platform) */
1635SOKOL_APP_API_DECL void sapp_consume_event(void);
1636/* get the current frame counter (for comparison with sapp_event.frame_count) */
1637SOKOL_APP_API_DECL uint64_t sapp_frame_count(void);
1638/* get an averaged/smoothed frame duration in seconds */
1639SOKOL_APP_API_DECL double sapp_frame_duration(void);
1640/* write string into clipboard */
1641SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str);
1642/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */
1643SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void);
1644/* set the window title (only on desktop platforms) */
1645SOKOL_APP_API_DECL void sapp_set_window_title(const char* str);
1646/* set the window icon (only on Windows and Linux) */
1647SOKOL_APP_API_DECL void sapp_set_icon(const sapp_icon_desc* icon_desc);
1648/* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */
1649SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void);
1650/* gets the dropped file paths */
1651SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index);
1652
1653/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */
1654SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc);
1655
1656/* EGL: get EGLDisplay object */
1657SOKOL_APP_API_DECL const void* sapp_egl_get_display(void);
1658/* EGL: get EGLContext object */
1659SOKOL_APP_API_DECL const void* sapp_egl_get_context(void);
1660
1661/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */
1662SOKOL_APP_API_DECL bool sapp_gles2(void);
1663
1664/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */
1665SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask);
1666/* HTML5: get byte size of a dropped file */
1667SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index);
1668/* HTML5: asynchronously load the content of a dropped file */
1669SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request);
1670
1671/* Metal: get bridged pointer to Metal device object */
1672SOKOL_APP_API_DECL const void* sapp_metal_get_device(void);
1673/* Metal: get bridged pointer to this frame's renderpass descriptor */
1674SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void);
1675/* Metal: get bridged pointer to current drawable */
1676SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void);
1677/* macOS: get bridged pointer to macOS NSWindow */
1678SOKOL_APP_API_DECL const void* sapp_macos_get_window(void);
1679/* iOS: get bridged pointer to iOS UIWindow */
1680SOKOL_APP_API_DECL const void* sapp_ios_get_window(void);
1681
1682/* D3D11: get pointer to ID3D11Device object */
1683SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void);
1684/* D3D11: get pointer to ID3D11DeviceContext object */
1685SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void);
1686/* D3D11: get pointer to IDXGISwapChain object */
1687SOKOL_APP_API_DECL const void* sapp_d3d11_get_swap_chain(void);
1688/* D3D11: get pointer to ID3D11RenderTargetView object */
1689SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void);
1690/* D3D11: get pointer to ID3D11DepthStencilView */
1691SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void);
1692/* Win32: get the HWND window handle */
1693SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void);
1694
1695/* WebGPU: get WGPUDevice handle */
1696SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void);
1697/* WebGPU: get swapchain's WGPUTextureView handle for rendering */
1698SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void);
1699/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */
1700SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void);
1701/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */
1702SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void);
1703
1704/* Android: get native activity handle */
1705SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void);
1706
1707#ifdef __cplusplus
1708} /* extern "C" */
1709
1710/* reference-based equivalents for C++ */
1711inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
1712
1713#endif
1714
1715// this WinRT specific hack is required when wWinMain is in a static library
1716#if defined(_MSC_VER) && defined(UNICODE)
1717#include <winapifamily.h>
1718#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
1719#pragma comment(linker, "/include:wWinMain")
1720#endif
1721#endif
1722
1723#endif // SOKOL_APP_INCLUDED
1724
1725/*-- IMPLEMENTATION ----------------------------------------------------------*/
1726#ifdef SOKOL_APP_IMPL
1727#define SOKOL_APP_IMPL_INCLUDED (1)
1728
1729#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE)
1730#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions"
1731#endif
1732
1733#include <stdlib.h> // malloc, free
1734#include <string.h> // memset
1735#include <stddef.h> // size_t
1736#include <math.h> /* roundf() */
1737
1738/* check if the config defines are alright */
1739#if defined(__APPLE__)
1740 // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting
1741 #if !defined(__cplusplus)
1742 #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
1743 #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
1744 #endif
1745 #endif
1746 #define _SAPP_APPLE (1)
1747 #include <TargetConditionals.h>
1748 #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
1749 /* MacOS */
1750 #define _SAPP_MACOS (1)
1751 #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33)
1752 #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33")
1753 #endif
1754 #else
1755 /* iOS or iOS Simulator */
1756 #define _SAPP_IOS (1)
1757 #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3)
1758 #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3")
1759 #endif
1760 #endif
1761#elif defined(__EMSCRIPTEN__)
1762 /* emscripten (asm.js or wasm) */
1763 #define _SAPP_EMSCRIPTEN (1)
1764 #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU)
1765 #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU")
1766 #endif
1767#elif defined(_WIN32)
1768 /* Windows (D3D11 or GL) */
1769 #include <winapifamily.h>
1770 #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
1771 #define _SAPP_UWP (1)
1772 #if !defined(SOKOL_D3D11)
1773 #error("sokol_app.h: unknown 3D API selected for UWP, must be SOKOL_D3D11")
1774 #endif
1775 #if !defined(__cplusplus)
1776 #error("sokol_app.h: UWP bindings require C++/17")
1777 #endif
1778 #else
1779 #define _SAPP_WIN32 (1)
1780 #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33)
1781 #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33")
1782 #endif
1783 #endif
1784#elif defined(__ANDROID__)
1785 /* Android */
1786 #define _SAPP_ANDROID (1)
1787 #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2)
1788 #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2")
1789 #endif
1790 #if defined(SOKOL_NO_ENTRY)
1791 #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android")
1792 #endif
1793#elif defined(__linux__) || defined(__unix__)
1794 /* Linux */
1795 #define _SAPP_LINUX (1)
1796 #if defined(SOKOL_GLCORE33)
1797 #if !defined(SOKOL_FORCE_EGL)
1798 #define _SAPP_GLX (1)
1799 #endif
1800 #elif !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2)
1801 #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3 or SOKOL_GLES2")
1802 #endif
1803#else
1804#error "sokol_app.h: Unknown platform"
1805#endif
1806
1807#ifndef SOKOL_API_IMPL
1808 #define SOKOL_API_IMPL
1809#endif
1810#ifndef SOKOL_DEBUG
1811 #ifndef NDEBUG
1812 #define SOKOL_DEBUG
1813 #endif
1814#endif
1815#ifndef SOKOL_ASSERT
1816 #include <assert.h>
1817 #define SOKOL_ASSERT(c) assert(c)
1818#endif
1819#ifndef SOKOL_UNREACHABLE
1820 #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
1821#endif
1822
1823#if !defined(SOKOL_DEBUG)
1824 #define SAPP_LOG(s)
1825#else
1826 #define SAPP_LOG(s) _sapp_log(s)
1827 #ifndef SOKOL_LOG
1828 #if defined(__ANDROID__)
1829 #include <android/log.h>
1830 #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s)
1831 #else
1832 #include <stdio.h>
1833 #define SOKOL_LOG(s) puts(s)
1834 #endif
1835 #endif
1836#endif
1837
1838#ifndef SOKOL_ABORT
1839 #define SOKOL_ABORT() abort()
1840#endif
1841#ifndef _SOKOL_PRIVATE
1842 #if defined(__GNUC__) || defined(__clang__)
1843 #define _SOKOL_PRIVATE __attribute__((unused)) static
1844 #else
1845 #define _SOKOL_PRIVATE static
1846 #endif
1847#endif
1848#ifndef _SOKOL_UNUSED
1849 #define _SOKOL_UNUSED(x) (void)(x)
1850#endif
1851
1852/*== PLATFORM SPECIFIC INCLUDES AND DEFINES ==================================*/
1853#if defined(_SAPP_APPLE)
1854 #if defined(SOKOL_METAL)
1855 #import <Metal/Metal.h>
1856 #import <MetalKit/MetalKit.h>
1857 #endif
1858 #if defined(_SAPP_MACOS)
1859 #if !defined(SOKOL_METAL)
1860 #ifndef GL_SILENCE_DEPRECATION
1861 #define GL_SILENCE_DEPRECATION
1862 #endif
1863 #include <Cocoa/Cocoa.h>
1864 #endif
1865 #elif defined(_SAPP_IOS)
1866 #import <UIKit/UIKit.h>
1867 #if !defined(SOKOL_METAL)
1868 #import <GLKit/GLKit.h>
1869 #endif
1870 #endif
1871 #include <AvailabilityMacros.h>
1872 #include <mach/mach_time.h>
1873#elif defined(_SAPP_EMSCRIPTEN)
1874 #if defined(SOKOL_WGPU)
1875 #include <webgpu/webgpu.h>
1876 #endif
1877 #include <emscripten/emscripten.h>
1878 #include <emscripten/html5.h>
1879#elif defined(_SAPP_WIN32)
1880 #ifdef _MSC_VER
1881 #pragma warning(push)
1882 #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */
1883 #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */
1884 #pragma warning(disable:4054) /* 'type cast': from function pointer */
1885 #pragma warning(disable:4055) /* 'type cast': from data pointer */
1886 #pragma warning(disable:4505) /* unreferenced local function has been removed */
1887 #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */
1888 #endif
1889 #ifndef WIN32_LEAN_AND_MEAN
1890 #define WIN32_LEAN_AND_MEAN
1891 #endif
1892 #ifndef NOMINMAX
1893 #define NOMINMAX
1894 #endif
1895 #include <windows.h>
1896 #include <windowsx.h>
1897 #include <shellapi.h>
1898 #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem
1899 #if defined(SOKOL_WIN32_FORCE_MAIN)
1900 #pragma comment (linker, "/subsystem:console")
1901 #else
1902 #pragma comment (linker, "/subsystem:windows")
1903 #endif
1904 #endif
1905 #include <stdio.h> /* freopen_s() */
1906 #include <wchar.h> /* wcslen() */
1907
1908 #pragma comment (lib, "kernel32")
1909 #pragma comment (lib, "user32")
1910 #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */
1911 #pragma comment (lib, "gdi32")
1912 #if defined(SOKOL_D3D11)
1913 #pragma comment (lib, "dxgi")
1914 #pragma comment (lib, "d3d11")
1915 #endif
1916
1917 #if defined(SOKOL_D3D11)
1918 #ifndef D3D11_NO_HELPERS
1919 #define D3D11_NO_HELPERS
1920 #endif
1921 #include <d3d11.h>
1922 #include <dxgi.h>
1923 // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it
1924 #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4)
1925 #endif
1926 #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */
1927 #define WM_MOUSEHWHEEL (0x020E)
1928 #endif
1929 #ifndef WM_DPICHANGED
1930 #define WM_DPICHANGED (0x02E0)
1931 #endif
1932#elif defined(_SAPP_UWP)
1933 #ifndef NOMINMAX
1934 #define NOMINMAX
1935 #endif
1936 #ifdef _MSC_VER
1937 #pragma warning(push)
1938 #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */
1939 #pragma warning(disable:4054) /* 'type cast': from function pointer */
1940 #pragma warning(disable:4055) /* 'type cast': from data pointer */
1941 #pragma warning(disable:4505) /* unreferenced local function has been removed */
1942 #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */
1943 #endif
1944 #include <windows.h>
1945 #include <winrt/Windows.ApplicationModel.Core.h>
1946 #include <winrt/Windows.Foundation.h>
1947 #include <winrt/Windows.Foundation.Collections.h>
1948 #include <winrt/Windows.Graphics.Display.h>
1949 #include <winrt/Windows.UI.Core.h>
1950 #include <winrt/Windows.UI.Composition.h>
1951 #include <winrt/Windows.UI.Input.h>
1952 #include <winrt/Windows.UI.ViewManagement.h>
1953 #include <winrt/Windows.System.h>
1954 #include <ppltasks.h>
1955
1956 #include <dxgi1_4.h>
1957 #include <d3d11_3.h>
1958 #include <DirectXMath.h>
1959
1960 #pragma comment (lib, "WindowsApp")
1961 #pragma comment (lib, "dxguid")
1962#elif defined(_SAPP_ANDROID)
1963 #include <pthread.h>
1964 #include <unistd.h>
1965 #include <time.h>
1966 #include <android/native_activity.h>
1967 #include <android/looper.h>
1968 #include <EGL/egl.h>
1969#elif defined(_SAPP_LINUX)
1970 #define GL_GLEXT_PROTOTYPES
1971 #include <X11/Xlib.h>
1972 #include <X11/Xutil.h>
1973 #include <X11/XKBlib.h>
1974 #include <X11/keysym.h>
1975 #include <X11/Xresource.h>
1976 #include <X11/Xatom.h>
1977 #include <X11/extensions/XInput2.h>
1978 #include <X11/Xcursor/Xcursor.h>
1979 #include <X11/cursorfont.h> /* XC_* font cursors */
1980 #include <X11/Xmd.h> /* CARD32 */
1981 #if !defined(_SAPP_GLX)
1982 #include <EGL/egl.h>
1983 #endif
1984 #include <dlfcn.h> /* dlopen, dlsym, dlclose */
1985 #include <limits.h> /* LONG_MAX */
1986 #include <pthread.h> /* only used a linker-guard, search for _sapp_linux_run() and see first comment */
1987 #include <time.h>
1988#endif
1989
1990/*== frame timing helpers ===================================================*/
1991#define _SAPP_RING_NUM_SLOTS (256)
1992typedef struct {
1993 int head;
1994 int tail;
1995 double buf[_SAPP_RING_NUM_SLOTS];
1996} _sapp_ring_t;
1997
1998_SOKOL_PRIVATE int _sapp_ring_idx(int i) {
1999 return i % _SAPP_RING_NUM_SLOTS;
2000}
2001
2002_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) {
2003 ring->head = 0;
2004 ring->tail = 0;
2005}
2006
2007_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) {
2008 return _sapp_ring_idx(ring->head + 1) == ring->tail;
2009}
2010
2011_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) {
2012 return ring->head == ring->tail;
2013}
2014
2015_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) {
2016 int count;
2017 if (ring->head >= ring->tail) {
2018 count = ring->head - ring->tail;
2019 }
2020 else {
2021 count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail;
2022 }
2023 SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS));
2024 return count;
2025}
2026
2027_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) {
2028 SOKOL_ASSERT(!_sapp_ring_full(ring));
2029 ring->buf[ring->head] = val;
2030 ring->head = _sapp_ring_idx(ring->head + 1);
2031}
2032
2033_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) {
2034 SOKOL_ASSERT(!_sapp_ring_empty(ring));
2035 double val = ring->buf[ring->tail];
2036 ring->tail = _sapp_ring_idx(ring->tail + 1);
2037 return val;
2038}
2039
2040/*
2041 NOTE:
2042
2043 Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS?
2044 A: The value appears to be highly unstable during the first few
2045 seconds, sometimes several frames are dropped in sequence, or
2046 switch between 120 and 60 Hz for a few frames. Simply measuring
2047 and averaging the frame time yielded a more stable frame duration.
2048 Maybe switching to CVDisplayLink would yield better results.
2049 Until then just measure the time.
2050*/
2051typedef struct {
2052 #if defined(_SAPP_APPLE)
2053 struct {
2054 mach_timebase_info_data_t timebase;
2055 uint64_t start;
2056 } mach;
2057 #elif defined(_SAPP_EMSCRIPTEN)
2058 // empty
2059 #elif defined(_SAPP_WIN32) || defined(_SAPP_UWP)
2060 struct {
2061 LARGE_INTEGER freq;
2062 LARGE_INTEGER start;
2063 } win;
2064 #else // Linux, Android, ...
2065 #ifdef CLOCK_MONOTONIC
2066 #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC
2067 #else
2068 // on some embedded platforms, CLOCK_MONOTONIC isn't defined
2069 #define _SAPP_CLOCK_MONOTONIC (1)
2070 #endif
2071 struct {
2072 uint64_t start;
2073 } posix;
2074 #endif
2075} _sapp_timestamp_t;
2076
2077_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) {
2078 int64_t q = value / denom;
2079 int64_t r = value % denom;
2080 return q * numer + r * numer / denom;
2081}
2082
2083_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) {
2084 #if defined(_SAPP_APPLE)
2085 mach_timebase_info(&ts->mach.timebase);
2086 ts->mach.start = mach_absolute_time();
2087 #elif defined(_SAPP_EMSCRIPTEN)
2088 (void)ts;
2089 #elif defined(_SAPP_WIN32) || defined(_SAPP_UWP)
2090 QueryPerformanceFrequency(&ts->win.freq);
2091 QueryPerformanceCounter(&ts->win.start);
2092 #else
2093 struct timespec tspec;
2094 clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec);
2095 ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec;
2096 #endif
2097}
2098
2099_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) {
2100 #if defined(_SAPP_APPLE)
2101 const uint64_t traw = mach_absolute_time() - ts->mach.start;
2102 const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom);
2103 return (double)now / 1000000000.0;
2104 #elif defined(_SAPP_EMSCRIPTEN)
2105 (void)ts;
2106 SOKOL_ASSERT(false);
2107 return 0.0;
2108 #elif defined(_SAPP_WIN32) || defined(_SAPP_UWP)
2109 LARGE_INTEGER qpc;
2110 QueryPerformanceCounter(&qpc);
2111 const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart);
2112 return (double)now / 1000000000.0;
2113 #else
2114 struct timespec tspec;
2115 clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec);
2116 const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start;
2117 return (double)now / 1000000000.0;
2118 #endif
2119}
2120
2121typedef struct {
2122 double last;
2123 double accum;
2124 double avg;
2125 int spike_count;
2126 int num;
2127 _sapp_timestamp_t timestamp;
2128 _sapp_ring_t ring;
2129} _sapp_timing_t;
2130
2131_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) {
2132 t->last = 0.0;
2133 t->accum = 0.0;
2134 t->spike_count = 0;
2135 t->num = 0;
2136 _sapp_ring_init(&t->ring);
2137}
2138
2139_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) {
2140 t->avg = 1.0 / 60.0; // dummy value until first actual value is available
2141 _sapp_timing_reset(t);
2142 _sapp_timestamp_init(&t->timestamp);
2143}
2144
2145_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) {
2146 // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging)
2147 double min_dur = 0.0;
2148 double max_dur = 0.1;
2149 // if we have enough samples for a useful average, use a much tighter 'valid window'
2150 if (_sapp_ring_full(&t->ring)) {
2151 min_dur = t->avg * 0.8;
2152 max_dur = t->avg * 1.2;
2153 }
2154 if ((dur < min_dur) || (dur > max_dur)) {
2155 t->spike_count++;
2156 // if there have been many spikes in a row, the display refresh rate
2157 // might have changed, so a timing reset is needed
2158 if (t->spike_count > 20) {
2159 _sapp_timing_reset(t);
2160 }
2161 return;
2162 }
2163 if (_sapp_ring_full(&t->ring)) {
2164 double old_val = _sapp_ring_dequeue(&t->ring);
2165 t->accum -= old_val;
2166 t->num -= 1;
2167 }
2168 _sapp_ring_enqueue(&t->ring, dur);
2169 t->accum += dur;
2170 t->num += 1;
2171 SOKOL_ASSERT(t->num > 0);
2172 t->avg = t->accum / t->num;
2173 t->spike_count = 0;
2174}
2175
2176_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) {
2177 t->last = 0.0;
2178}
2179
2180_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) {
2181 const double now = _sapp_timestamp_now(&t->timestamp);
2182 if (t->last > 0.0) {
2183 double dur = now - t->last;
2184 _sapp_timing_put(t, dur);
2185 }
2186 t->last = now;
2187}
2188
2189_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) {
2190 if (t->last > 0.0) {
2191 double dur = now - t->last;
2192 _sapp_timing_put(t, dur);
2193 }
2194 t->last = now;
2195}
2196
2197_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) {
2198 return t->avg;
2199}
2200
2201/*== MACOS DECLARATIONS ======================================================*/
2202#if defined(_SAPP_MACOS)
2203
2204// __v_ start
2205@interface SokolWindow : NSWindow {
2206}
2207@end
2208@interface MyView2 : NSView
2209@end
2210
2211MyView2* g_view;
2212
2213// A custom NSWindow interface to handle events in borderless windows.
2214@implementation SokolWindow
2215- (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless
2216- (BOOL)canBecomeMainWindow { return YES; }
2217@end
2218// __v_ end
2219
2220
2221@interface _sapp_macos_app_delegate : NSObject<NSApplicationDelegate>
2222@end
2223@interface _sapp_macos_window : NSWindow
2224@end
2225@interface _sapp_macos_window_delegate : NSObject<NSWindowDelegate>
2226@end
2227#if defined(SOKOL_METAL)
2228 @interface _sapp_macos_view : MTKView
2229 @end
2230#elif defined(SOKOL_GLCORE33)
2231 @interface _sapp_macos_view : NSOpenGLView
2232 - (void)timerFired:(id)sender;
2233 @end
2234#endif // SOKOL_GLCORE33
2235
2236typedef struct {
2237 uint32_t flags_changed_store;
2238 uint8_t mouse_buttons;
2239 // __v_ start
2240 //NSWindow* window; // __v_ removed
2241 _sapp_macos_window* window; // __v_ added
2242 // __v_ end
2243 NSTrackingArea* tracking_area;
2244 _sapp_macos_app_delegate* app_dlg;
2245 _sapp_macos_window_delegate* win_dlg;
2246 _sapp_macos_view* view;
2247 NSCursor* cursors[_SAPP_MOUSECURSOR_NUM];
2248 #if defined(SOKOL_METAL)
2249 id<MTLDevice> mtl_device;
2250 #endif
2251} _sapp_macos_t;
2252
2253#endif // _SAPP_MACOS
2254
2255/*== IOS DECLARATIONS ========================================================*/
2256#if defined(_SAPP_IOS)
2257
2258@interface _sapp_app_delegate : NSObject<UIApplicationDelegate>
2259@end
2260@interface _sapp_textfield_dlg : NSObject<UITextFieldDelegate>
2261- (void)keyboardWasShown:(NSNotification*)notif;
2262- (void)keyboardWillBeHidden:(NSNotification*)notif;
2263- (void)keyboardDidChangeFrame:(NSNotification*)notif;
2264@end
2265#if defined(SOKOL_METAL)
2266 @interface _sapp_ios_view : MTKView;
2267 @end
2268#else
2269 @interface _sapp_ios_view : GLKView
2270 @end
2271#endif
2272
2273typedef struct {
2274 UIWindow* window;
2275 _sapp_ios_view* view;
2276 UITextField* textfield;
2277 _sapp_textfield_dlg* textfield_dlg;
2278 #if defined(SOKOL_METAL)
2279 UIViewController* view_ctrl;
2280 id<MTLDevice> mtl_device;
2281 #else
2282 GLKViewController* view_ctrl;
2283 EAGLContext* eagl_ctx;
2284 #endif
2285 bool suspended;
2286} _sapp_ios_t;
2287
2288#endif // _SAPP_IOS
2289
2290/*== EMSCRIPTEN DECLARATIONS =================================================*/
2291#if defined(_SAPP_EMSCRIPTEN)
2292
2293#if defined(SOKOL_WGPU)
2294typedef struct {
2295 int state;
2296 WGPUDevice device;
2297 WGPUSwapChain swapchain;
2298 WGPUTextureFormat render_format;
2299 WGPUTexture msaa_tex;
2300 WGPUTexture depth_stencil_tex;
2301 WGPUTextureView swapchain_view;
2302 WGPUTextureView msaa_view;
2303 WGPUTextureView depth_stencil_view;
2304} _sapp_wgpu_t;
2305#endif
2306
2307typedef struct {
2308 bool textfield_created;
2309 bool wants_show_keyboard;
2310 bool wants_hide_keyboard;
2311 bool mouse_lock_requested;
2312 uint16_t mouse_buttons;
2313 #if defined(SOKOL_WGPU)
2314 _sapp_wgpu_t wgpu;
2315 #endif
2316} _sapp_emsc_t;
2317#endif // _SAPP_EMSCRIPTEN
2318
2319/*== WIN32 DECLARATIONS ======================================================*/
2320#if defined(SOKOL_D3D11) && (defined(_SAPP_WIN32) || defined(_SAPP_UWP))
2321typedef struct {
2322 ID3D11Device* device;
2323 ID3D11DeviceContext* device_context;
2324 ID3D11Texture2D* rt;
2325 ID3D11RenderTargetView* rtv;
2326 ID3D11Texture2D* msaa_rt;
2327 ID3D11RenderTargetView* msaa_rtv;
2328 ID3D11Texture2D* ds;
2329 ID3D11DepthStencilView* dsv;
2330 DXGI_SWAP_CHAIN_DESC swap_chain_desc;
2331 IDXGISwapChain* swap_chain;
2332 IDXGIDevice1* dxgi_device;
2333 bool use_dxgi_frame_stats;
2334 UINT sync_refresh_count;
2335} _sapp_d3d11_t;
2336#endif
2337
2338/*== WIN32 DECLARATIONS ======================================================*/
2339#if defined(_SAPP_WIN32)
2340
2341#ifndef DPI_ENUMS_DECLARED
2342typedef enum PROCESS_DPI_AWARENESS
2343{
2344 PROCESS_DPI_UNAWARE = 0,
2345 PROCESS_SYSTEM_DPI_AWARE = 1,
2346 PROCESS_PER_MONITOR_DPI_AWARE = 2
2347} PROCESS_DPI_AWARENESS;
2348typedef enum MONITOR_DPI_TYPE {
2349 MDT_EFFECTIVE_DPI = 0,
2350 MDT_ANGULAR_DPI = 1,
2351 MDT_RAW_DPI = 2,
2352 MDT_DEFAULT = MDT_EFFECTIVE_DPI
2353} MONITOR_DPI_TYPE;
2354#endif /*DPI_ENUMS_DECLARED*/
2355
2356typedef struct {
2357 bool aware;
2358 float content_scale;
2359 float window_scale;
2360 float mouse_scale;
2361} _sapp_win32_dpi_t;
2362
2363typedef struct {
2364 HWND hwnd;
2365 HMONITOR hmonitor;
2366 HDC dc;
2367 HICON big_icon;
2368 HICON small_icon;
2369 HCURSOR cursors[_SAPP_MOUSECURSOR_NUM];
2370 UINT orig_codepage;
2371 LONG mouse_locked_x, mouse_locked_y;
2372 RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed
2373 bool is_win10_or_greater;
2374 bool in_create_window;
2375 bool iconified;
2376 bool mouse_tracked;
2377 uint8_t mouse_capture_mask;
2378 _sapp_win32_dpi_t dpi;
2379 bool raw_input_mousepos_valid;
2380 LONG raw_input_mousepos_x;
2381 LONG raw_input_mousepos_y;
2382 uint8_t raw_input_data[256];
2383} _sapp_win32_t;
2384
2385#if defined(SOKOL_GLCORE33)
2386#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
2387#define WGL_SUPPORT_OPENGL_ARB 0x2010
2388#define WGL_DRAW_TO_WINDOW_ARB 0x2001
2389#define WGL_PIXEL_TYPE_ARB 0x2013
2390#define WGL_TYPE_RGBA_ARB 0x202b
2391#define WGL_ACCELERATION_ARB 0x2003
2392#define WGL_NO_ACCELERATION_ARB 0x2025
2393#define WGL_RED_BITS_ARB 0x2015
2394#define WGL_GREEN_BITS_ARB 0x2017
2395#define WGL_BLUE_BITS_ARB 0x2019
2396#define WGL_ALPHA_BITS_ARB 0x201b
2397#define WGL_DEPTH_BITS_ARB 0x2022
2398#define WGL_STENCIL_BITS_ARB 0x2023
2399#define WGL_DOUBLE_BUFFER_ARB 0x2011
2400#define WGL_SAMPLES_ARB 0x2042
2401#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
2402#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
2403#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
2404#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
2405#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
2406#define WGL_CONTEXT_FLAGS_ARB 0x2094
2407#define ERROR_INVALID_VERSION_ARB 0x2095
2408#define ERROR_INVALID_PROFILE_ARB 0x2096
2409#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054
2410typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int);
2411typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*);
2412typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void);
2413typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC);
2414typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*);
2415typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC);
2416typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC);
2417typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR);
2418typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void);
2419typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC);
2420
2421typedef struct {
2422 HINSTANCE opengl32;
2423 HGLRC gl_ctx;
2424 PFN_wglCreateContext CreateContext;
2425 PFN_wglDeleteContext DeleteContext;
2426 PFN_wglGetProcAddress GetProcAddress;
2427 PFN_wglGetCurrentDC GetCurrentDC;
2428 PFN_wglMakeCurrent MakeCurrent;
2429 PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT;
2430 PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB;
2431 PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT;
2432 PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB;
2433 PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB;
2434 bool ext_swap_control;
2435 bool arb_multisample;
2436 bool arb_pixel_format;
2437 bool arb_create_context;
2438 bool arb_create_context_profile;
2439 HWND msg_hwnd;
2440 HDC msg_dc;
2441} _sapp_wgl_t;
2442#endif // SOKOL_GLCORE33
2443
2444#endif // _SAPP_WIN32
2445
2446/*== UWP DECLARATIONS ======================================================*/
2447#if defined(_SAPP_UWP)
2448
2449typedef struct {
2450 float content_scale;
2451 float window_scale;
2452 float mouse_scale;
2453} _sapp_uwp_dpi_t;
2454
2455typedef struct {
2456 bool mouse_tracked;
2457 uint8_t mouse_buttons;
2458 _sapp_uwp_dpi_t dpi;
2459} _sapp_uwp_t;
2460
2461#endif // _SAPP_UWP
2462
2463/*== ANDROID DECLARATIONS ====================================================*/
2464
2465#if defined(_SAPP_ANDROID)
2466typedef enum {
2467 _SOKOL_ANDROID_MSG_CREATE,
2468 _SOKOL_ANDROID_MSG_RESUME,
2469 _SOKOL_ANDROID_MSG_PAUSE,
2470 _SOKOL_ANDROID_MSG_FOCUS,
2471 _SOKOL_ANDROID_MSG_NO_FOCUS,
2472 _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW,
2473 _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE,
2474 _SOKOL_ANDROID_MSG_DESTROY,
2475} _sapp_android_msg_t;
2476
2477typedef struct {
2478 pthread_t thread;
2479 pthread_mutex_t mutex;
2480 pthread_cond_t cond;
2481 int read_from_main_fd;
2482 int write_from_main_fd;
2483} _sapp_android_pt_t;
2484
2485typedef struct {
2486 ANativeWindow* window;
2487 AInputQueue* input;
2488} _sapp_android_resources_t;
2489
2490typedef struct {
2491 ANativeActivity* activity;
2492 _sapp_android_pt_t pt;
2493 _sapp_android_resources_t pending;
2494 _sapp_android_resources_t current;
2495 ALooper* looper;
2496 bool is_thread_started;
2497 bool is_thread_stopping;
2498 bool is_thread_stopped;
2499 bool has_created;
2500 bool has_resumed;
2501 bool has_focus;
2502 EGLConfig config;
2503 EGLDisplay display;
2504 EGLContext context;
2505 EGLSurface surface;
2506} _sapp_android_t;
2507
2508#endif // _SAPP_ANDROID
2509
2510/*== LINUX DECLARATIONS ======================================================*/
2511#if defined(_SAPP_LINUX)
2512
2513#define _SAPP_X11_XDND_VERSION (5)
2514
2515#define GLX_VENDOR 1
2516#define GLX_RGBA_BIT 0x00000001
2517#define GLX_WINDOW_BIT 0x00000001
2518#define GLX_DRAWABLE_TYPE 0x8010
2519#define GLX_RENDER_TYPE 0x8011
2520#define GLX_DOUBLEBUFFER 5
2521#define GLX_RED_SIZE 8
2522#define GLX_GREEN_SIZE 9
2523#define GLX_BLUE_SIZE 10
2524#define GLX_ALPHA_SIZE 11
2525#define GLX_DEPTH_SIZE 12
2526#define GLX_STENCIL_SIZE 13
2527#define GLX_SAMPLES 0x186a1
2528#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
2529#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
2530#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
2531#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
2532#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
2533#define GLX_CONTEXT_FLAGS_ARB 0x2094
2534
2535typedef XID GLXWindow;
2536typedef XID GLXDrawable;
2537typedef struct __GLXFBConfig* GLXFBConfig;
2538typedef struct __GLXcontext* GLXContext;
2539typedef void (*__GLXextproc)(void);
2540
2541typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*);
2542typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int);
2543typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*);
2544typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*);
2545typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext);
2546typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext);
2547typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable);
2548typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int);
2549typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*);
2550typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName);
2551typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int);
2552typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig);
2553typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*);
2554typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow);
2555
2556typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int);
2557typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*);
2558
2559typedef struct {
2560 bool available;
2561 int major_opcode;
2562 int event_base;
2563 int error_base;
2564 int major;
2565 int minor;
2566} _sapp_xi_t;
2567
2568typedef struct {
2569 int version;
2570 Window source;
2571 Atom format;
2572 Atom XdndAware;
2573 Atom XdndEnter;
2574 Atom XdndPosition;
2575 Atom XdndStatus;
2576 Atom XdndActionCopy;
2577 Atom XdndDrop;
2578 Atom XdndFinished;
2579 Atom XdndSelection;
2580 Atom XdndTypeList;
2581 Atom text_uri_list;
2582} _sapp_xdnd_t;
2583
2584typedef struct {
2585 uint8_t mouse_buttons;
2586 Display* display;
2587 int screen;
2588 Window root;
2589 Colormap colormap;
2590 Window window;
2591 Cursor hidden_cursor;
2592 Cursor cursors[_SAPP_MOUSECURSOR_NUM];
2593 int window_state;
2594 float dpi;
2595 unsigned char error_code;
2596 Atom UTF8_STRING;
2597 Atom WM_PROTOCOLS;
2598 Atom WM_DELETE_WINDOW;
2599 Atom WM_STATE;
2600 Atom NET_WM_NAME;
2601 Atom NET_WM_ICON_NAME;
2602 Atom NET_WM_ICON;
2603 Atom NET_WM_STATE;
2604 Atom NET_WM_STATE_FULLSCREEN;
2605 _sapp_xi_t xi;
2606 _sapp_xdnd_t xdnd;
2607} _sapp_x11_t;
2608
2609#if defined(_SAPP_GLX)
2610
2611typedef struct {
2612 void* libgl;
2613 int major;
2614 int minor;
2615 int event_base;
2616 int error_base;
2617 GLXContext ctx;
2618 GLXWindow window;
2619
2620 // GLX 1.3 functions
2621 PFNGLXGETFBCONFIGSPROC GetFBConfigs;
2622 PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib;
2623 PFNGLXGETCLIENTSTRINGPROC GetClientString;
2624 PFNGLXQUERYEXTENSIONPROC QueryExtension;
2625 PFNGLXQUERYVERSIONPROC QueryVersion;
2626 PFNGLXDESTROYCONTEXTPROC DestroyContext;
2627 PFNGLXMAKECURRENTPROC MakeCurrent;
2628 PFNGLXSWAPBUFFERSPROC SwapBuffers;
2629 PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString;
2630 PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig;
2631 PFNGLXCREATEWINDOWPROC CreateWindow;
2632 PFNGLXDESTROYWINDOWPROC DestroyWindow;
2633
2634 // GLX 1.4 and extension functions
2635 PFNGLXGETPROCADDRESSPROC GetProcAddress;
2636 PFNGLXGETPROCADDRESSPROC GetProcAddressARB;
2637 PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT;
2638 PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA;
2639 PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB;
2640
2641 // extension availability
2642 bool EXT_swap_control;
2643 bool MESA_swap_control;
2644 bool ARB_multisample;
2645 bool ARB_create_context;
2646 bool ARB_create_context_profile;
2647} _sapp_glx_t;
2648
2649#else
2650
2651typedef struct {
2652 EGLDisplay display;
2653 EGLContext context;
2654 EGLSurface surface;
2655} _sapp_egl_t;
2656
2657#endif // _SAPP_GLX
2658
2659#endif // _SAPP_LINUX
2660
2661/*== COMMON DECLARATIONS =====================================================*/
2662
2663/* helper macros */
2664#define _sapp_def(val, def) (((val) == 0) ? (def) : (val))
2665#define _sapp_absf(a) (((a)<0.0f)?-(a):(a))
2666
2667#define _SAPP_MAX_TITLE_LENGTH (128)
2668#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640)
2669#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480)
2670/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */
2671#define _SAPP_PIXELFORMAT_RGBA8 (23)
2672#define _SAPP_PIXELFORMAT_BGRA8 (27)
2673#define _SAPP_PIXELFORMAT_DEPTH (41)
2674#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (42)
2675
2676#if defined(_SAPP_MACOS) || defined(_SAPP_IOS)
2677 // this is ARC compatible
2678 #if defined(__cplusplus)
2679 #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); }
2680 #else
2681 #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; }
2682 #endif
2683#else
2684 #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); }
2685#endif
2686
2687typedef struct {
2688 bool enabled;
2689 int buf_size;
2690 char* buffer;
2691} _sapp_clipboard_t;
2692
2693typedef struct {
2694 bool enabled;
2695 int max_files;
2696 int max_path_length;
2697 int num_files;
2698 int buf_size;
2699 char* buffer;
2700} _sapp_drop_t;
2701
2702typedef struct {
2703 float x, y;
2704 float dx, dy;
2705 bool shown;
2706 bool locked;
2707 bool pos_valid;
2708 sapp_mouse_cursor current_cursor;
2709} _sapp_mouse_t;
2710
2711typedef struct {
2712 sapp_desc desc;
2713 bool valid;
2714 bool fullscreen;
2715 bool gles2_fallback;
2716 bool first_frame;
2717 bool init_called;
2718 bool cleanup_called;
2719 bool quit_requested;
2720 bool quit_ordered;
2721 bool event_consumed;
2722 bool html5_ask_leave_site;
2723 bool onscreen_keyboard_shown;
2724 int window_width;
2725 int window_height;
2726 int framebuffer_width;
2727 int framebuffer_height;
2728 int sample_count;
2729 int swap_interval;
2730 float dpi_scale;
2731 uint64_t frame_count;
2732 _sapp_timing_t timing;
2733 sapp_event event;
2734 _sapp_mouse_t mouse;
2735 _sapp_clipboard_t clipboard;
2736 _sapp_drop_t drop;
2737 sapp_icon_desc default_icon_desc;
2738 uint32_t* default_icon_pixels;
2739 #if defined(_SAPP_MACOS)
2740 _sapp_macos_t macos;
2741 #elif defined(_SAPP_IOS)
2742 _sapp_ios_t ios;
2743 #elif defined(_SAPP_EMSCRIPTEN)
2744 _sapp_emsc_t emsc;
2745 #elif defined(_SAPP_WIN32)
2746 _sapp_win32_t win32;
2747 #if defined(SOKOL_D3D11)
2748 _sapp_d3d11_t d3d11;
2749 #elif defined(SOKOL_GLCORE33)
2750 _sapp_wgl_t wgl;
2751 #endif
2752 #elif defined(_SAPP_UWP)
2753 _sapp_uwp_t uwp;
2754 #if defined(SOKOL_D3D11)
2755 _sapp_d3d11_t d3d11;
2756 #endif
2757 #elif defined(_SAPP_ANDROID)
2758 _sapp_android_t android;
2759 #elif defined(_SAPP_LINUX)
2760 _sapp_x11_t x11;
2761 #if defined(_SAPP_GLX)
2762 _sapp_glx_t glx;
2763 #else
2764 _sapp_egl_t egl;
2765 #endif
2766 #endif
2767 char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH];
2768 char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */
2769 wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */
2770 sapp_keycode keycodes[SAPP_MAX_KEYCODES];
2771 // __v_ start
2772 bool __v_native_render; /* V patch to allow for native rendering */
2773 // __v_ end
2774} _sapp_t;
2775static _sapp_t _sapp;
2776
2777/*=== PRIVATE HELPER FUNCTIONS ===============================================*/
2778_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) {
2779 SOKOL_ASSERT(ptr && (size > 0));
2780 memset(ptr, 0, size);
2781}
2782
2783_SOKOL_PRIVATE void* _sapp_malloc(size_t size) {
2784 SOKOL_ASSERT(size > 0);
2785 void* ptr;
2786 if (_sapp.desc.allocator.alloc) {
2787 ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data);
2788 }
2789 else {
2790 ptr = malloc(size);
2791 }
2792 SOKOL_ASSERT(ptr);
2793 return ptr;
2794}
2795
2796_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) {
2797 void* ptr = _sapp_malloc(size);
2798 _sapp_clear(ptr, size);
2799 return ptr;
2800}
2801
2802_SOKOL_PRIVATE void _sapp_free(void* ptr) {
2803 if (_sapp.desc.allocator.free) {
2804 _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data);
2805 }
2806 else {
2807 free(ptr);
2808 }
2809}
2810
2811#if defined(SOKOL_DEBUG)
2812_SOKOL_PRIVATE void _sapp_log(const char* msg) {
2813 SOKOL_ASSERT(msg);
2814 if (_sapp.desc.logger.log_cb) {
2815 _sapp.desc.logger.log_cb(msg, _sapp.desc.logger.user_data);
2816 }
2817 else {
2818 SOKOL_LOG(msg);
2819 }
2820}
2821#endif
2822
2823_SOKOL_PRIVATE void _sapp_fail(const char* msg) {
2824 if (_sapp.desc.fail_cb) {
2825 _sapp.desc.fail_cb(msg);
2826 }
2827 else if (_sapp.desc.fail_userdata_cb) {
2828 _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data);
2829 }
2830 else {
2831 SAPP_LOG(msg);
2832 }
2833 SOKOL_ABORT();
2834}
2835
2836_SOKOL_PRIVATE void _sapp_call_init(void) {
2837 if (_sapp.desc.init_cb) {
2838 _sapp.desc.init_cb();
2839 }
2840 else if (_sapp.desc.init_userdata_cb) {
2841 _sapp.desc.init_userdata_cb(_sapp.desc.user_data);
2842 }
2843 _sapp.init_called = true;
2844}
2845
2846_SOKOL_PRIVATE void _sapp_call_frame(void) {
2847 // __v_ start
2848 if (_sapp.__v_native_render) {
2849 return;
2850 }
2851 // __v_ end
2852 if (_sapp.init_called && !_sapp.cleanup_called) {
2853 if (_sapp.desc.frame_cb) {
2854 _sapp.desc.frame_cb();
2855 }
2856 else if (_sapp.desc.frame_userdata_cb) {
2857 _sapp.desc.frame_userdata_cb(_sapp.desc.user_data);
2858 }
2859 }
2860}
2861
2862// __v_ start
2863_SOKOL_PRIVATE void _sapp_call_frame_native(void) {
2864 //puts("_sapp_call_frame_native()");
2865 //printf("init called=%d cleanup_called=%d\n", _sapp.init_called,_sapp.cleanup_called);
2866 if (_sapp.init_called && !_sapp.cleanup_called) {
2867 if (_sapp.desc.frame_cb) {
2868 _sapp.desc.frame_cb();
2869 }
2870 else if (_sapp.desc.frame_userdata_cb) {
2871 _sapp.desc.frame_userdata_cb(_sapp.desc.user_data);
2872 }
2873 }
2874}
2875// __v_ end
2876
2877_SOKOL_PRIVATE void _sapp_call_cleanup(void) {
2878 if (!_sapp.cleanup_called) {
2879 if (_sapp.desc.cleanup_cb) {
2880 _sapp.desc.cleanup_cb();
2881 }
2882 else if (_sapp.desc.cleanup_userdata_cb) {
2883 _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data);
2884 }
2885 _sapp.cleanup_called = true;
2886 }
2887}
2888
2889_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) {
2890 if (!_sapp.cleanup_called) {
2891 if (_sapp.desc.event_cb) {
2892 _sapp.desc.event_cb(e);
2893 }
2894 else if (_sapp.desc.event_userdata_cb) {
2895 _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data);
2896 }
2897 }
2898 if (_sapp.event_consumed) {
2899 _sapp.event_consumed = false;
2900 return true;
2901 }
2902 else {
2903 return false;
2904 }
2905}
2906
2907_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) {
2908 SOKOL_ASSERT(_sapp.drop.buffer);
2909 SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files));
2910 int offset = index * _sapp.drop.max_path_length;
2911 SOKOL_ASSERT(offset < _sapp.drop.buf_size);
2912 return &_sapp.drop.buffer[offset];
2913}
2914
2915/* Copy a string into a fixed size buffer with guaranteed zero-
2916 termination.
2917
2918 Return false if the string didn't fit into the buffer and had to be clamped.
2919
2920 FIXME: Currently UTF-8 strings might become invalid if the string
2921 is clamped, because the last zero-byte might be written into
2922 the middle of a multi-byte sequence.
2923*/
2924_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) {
2925 SOKOL_ASSERT(src && dst && (max_len > 0));
2926 char* const end = &(dst[max_len-1]);
2927 char c = 0;
2928 for (int i = 0; i < max_len; i++) {
2929 c = *src;
2930 if (c != 0) {
2931 src++;
2932 }
2933 *dst++ = c;
2934 }
2935 /* truncated? */
2936 if (c != 0) {
2937 *end = 0;
2938 return false;
2939 }
2940 else {
2941 return true;
2942 }
2943}
2944
2945_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) {
2946 SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
2947 sapp_desc res = *desc;
2948 res.sample_count = _sapp_def(res.sample_count, 1);
2949 res.swap_interval = _sapp_def(res.swap_interval, 1);
2950 // NOTE: can't patch the default for gl_major_version and gl_minor_version
2951 // independently, because a desired version 4.0 would be patched to 4.2
2952 // (or expressed differently: zero is a valid value for gl_minor_version
2953 // and can't be used to indicate 'default')
2954 if (0 == res.gl_major_version) {
2955 res.gl_major_version = 3;
2956 res.gl_minor_version = 2;
2957 }
2958 res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas");
2959 res.clipboard_size = _sapp_def(res.clipboard_size, 8192);
2960 res.max_dropped_files = _sapp_def(res.max_dropped_files, 1);
2961 res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048);
2962 res.window_title = _sapp_def(res.window_title, "sokol_app");
2963 return res;
2964}
2965
2966_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) {
2967 SOKOL_ASSERT(desc);
2968 SOKOL_ASSERT(desc->width >= 0);
2969 SOKOL_ASSERT(desc->height >= 0);
2970 SOKOL_ASSERT(desc->sample_count >= 0);
2971 SOKOL_ASSERT(desc->swap_interval >= 0);
2972 SOKOL_ASSERT(desc->clipboard_size >= 0);
2973 SOKOL_ASSERT(desc->max_dropped_files >= 0);
2974 SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0);
2975 _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp);
2976 _sapp.desc = _sapp_desc_defaults(desc);
2977 _sapp.first_frame = true;
2978 // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this
2979 _sapp.window_width = _sapp.desc.width;
2980 _sapp.window_height = _sapp.desc.height;
2981 _sapp.framebuffer_width = _sapp.window_width;
2982 _sapp.framebuffer_height = _sapp.window_height;
2983 _sapp.sample_count = _sapp.desc.sample_count;
2984 _sapp.swap_interval = _sapp.desc.swap_interval;
2985 _sapp.html5_canvas_selector[0] = '#';
2986 _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1);
2987 _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1];
2988 _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site;
2989 _sapp.clipboard.enabled = _sapp.desc.enable_clipboard;
2990 if (_sapp.clipboard.enabled) {
2991 _sapp.clipboard.buf_size = _sapp.desc.clipboard_size;
2992 _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size);
2993 }
2994 _sapp.drop.enabled = _sapp.desc.enable_dragndrop;
2995 if (_sapp.drop.enabled) {
2996 _sapp.drop.max_files = _sapp.desc.max_dropped_files;
2997 _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length;
2998 _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length;
2999 _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size);
3000 }
3001 _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title));
3002 _sapp.desc.window_title = _sapp.window_title;
3003 _sapp.dpi_scale = 1.0f;
3004 _sapp.fullscreen = _sapp.desc.fullscreen;
3005 _sapp.mouse.shown = true;
3006 _sapp_timing_init(&_sapp.timing);
3007 // __v_ start
3008 _sapp.__v_native_render = _sapp.desc.__v_native_render;
3009 // __v_end
3010}
3011
3012_SOKOL_PRIVATE void _sapp_discard_state(void) {
3013 if (_sapp.clipboard.enabled) {
3014 SOKOL_ASSERT(_sapp.clipboard.buffer);
3015 _sapp_free((void*)_sapp.clipboard.buffer);
3016 }
3017 if (_sapp.drop.enabled) {
3018 SOKOL_ASSERT(_sapp.drop.buffer);
3019 _sapp_free((void*)_sapp.drop.buffer);
3020 }
3021 if (_sapp.default_icon_pixels) {
3022 _sapp_free((void*)_sapp.default_icon_pixels);
3023 }
3024 _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp);
3025}
3026
3027_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) {
3028 _sapp_clear(&_sapp.event, sizeof(_sapp.event));
3029 _sapp.event.type = type;
3030 _sapp.event.frame_count = _sapp.frame_count;
3031 _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID;
3032 _sapp.event.window_width = _sapp.window_width;
3033 _sapp.event.window_height = _sapp.window_height;
3034 _sapp.event.framebuffer_width = _sapp.framebuffer_width;
3035 _sapp.event.framebuffer_height = _sapp.framebuffer_height;
3036 _sapp.event.mouse_x = _sapp.mouse.x;
3037 _sapp.event.mouse_y = _sapp.mouse.y;
3038 _sapp.event.mouse_dx = _sapp.mouse.dx;
3039 _sapp.event.mouse_dy = _sapp.mouse.dy;
3040}
3041
3042_SOKOL_PRIVATE bool _sapp_events_enabled(void) {
3043 /* only send events when an event callback is set, and the init function was called */
3044 return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called;
3045}
3046
3047_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) {
3048 if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) {
3049 return _sapp.keycodes[scan_code];
3050 }
3051 else {
3052 return SAPP_KEYCODE_INVALID;
3053 }
3054}
3055
3056_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) {
3057 if (_sapp.drop.enabled) {
3058 SOKOL_ASSERT(_sapp.drop.buffer);
3059 _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size);
3060 }
3061}
3062
3063_SOKOL_PRIVATE void _sapp_frame(void) {
3064 if (_sapp.first_frame) {
3065 _sapp.first_frame = false;
3066 _sapp_call_init();
3067 }
3068 _sapp_call_frame();
3069 _sapp.frame_count++;
3070}
3071
3072_SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) {
3073 SOKOL_ASSERT(desc->width > 0);
3074 SOKOL_ASSERT(desc->height > 0);
3075 SOKOL_ASSERT(desc->pixels.ptr != 0);
3076 SOKOL_ASSERT(desc->pixels.size > 0);
3077 const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t);
3078 if (wh_size != desc->pixels.size) {
3079 SAPP_LOG("Image data size mismatch (must be width*height*4 bytes)\n");
3080 return false;
3081 }
3082 return true;
3083}
3084
3085_SOKOL_PRIVATE int _sapp_image_bestmatch(const sapp_image_desc image_descs[], int num_images, int width, int height) {
3086 int least_diff = 0x7FFFFFFF;
3087 int least_index = 0;
3088 for (int i = 0; i < num_images; i++) {
3089 int diff = (image_descs[i].width * image_descs[i].height) - (width * height);
3090 if (diff < 0) {
3091 diff = -diff;
3092 }
3093 if (diff < least_diff) {
3094 least_diff = diff;
3095 least_index = i;
3096 }
3097 }
3098 return least_index;
3099}
3100
3101_SOKOL_PRIVATE int _sapp_icon_num_images(const sapp_icon_desc* desc) {
3102 int index = 0;
3103 for (; index < SAPP_MAX_ICONIMAGES; index++) {
3104 if (0 == desc->images[index].pixels.ptr) {
3105 break;
3106 }
3107 }
3108 return index;
3109}
3110
3111_SOKOL_PRIVATE bool _sapp_validate_icon_desc(const sapp_icon_desc* desc, int num_images) {
3112 SOKOL_ASSERT(num_images <= SAPP_MAX_ICONIMAGES);
3113 for (int i = 0; i < num_images; i++) {
3114 const sapp_image_desc* img_desc = &desc->images[i];
3115 if (!_sapp_image_validate(img_desc)) {
3116 return false;
3117 }
3118 }
3119 return true;
3120}
3121
3122_SOKOL_PRIVATE void _sapp_setup_default_icon(void) {
3123 SOKOL_ASSERT(0 == _sapp.default_icon_pixels);
3124
3125 const int num_icons = 3;
3126 const int icon_sizes[3] = { 16, 32, 64 }; // must be multiple of 8!
3127
3128 // allocate a pixel buffer for all icon pixels
3129 int all_num_pixels = 0;
3130 for (int i = 0; i < num_icons; i++) {
3131 all_num_pixels += icon_sizes[i] * icon_sizes[i];
3132 }
3133 _sapp.default_icon_pixels = (uint32_t*) _sapp_malloc_clear((size_t)all_num_pixels * sizeof(uint32_t));
3134
3135 // initialize default_icon_desc struct
3136 uint32_t* dst = _sapp.default_icon_pixels;
3137 const uint32_t* dst_end = dst + all_num_pixels;
3138 (void)dst_end; // silence unused warning in release mode
3139 for (int i = 0; i < num_icons; i++) {
3140 const int dim = (int) icon_sizes[i];
3141 const int num_pixels = dim * dim;
3142 sapp_image_desc* img_desc = &_sapp.default_icon_desc.images[i];
3143 img_desc->width = dim;
3144 img_desc->height = dim;
3145 img_desc->pixels.ptr = dst;
3146 img_desc->pixels.size = (size_t)num_pixels * sizeof(uint32_t);
3147 dst += num_pixels;
3148 }
3149 SOKOL_ASSERT(dst == dst_end);
3150
3151 // Amstrad CPC font 'S'
3152 const uint8_t tile[8] = {
3153 0x3C,
3154 0x66,
3155 0x60,
3156 0x3C,
3157 0x06,
3158 0x66,
3159 0x3C,
3160 0x00,
3161 };
3162 // rainbow colors
3163 const uint32_t colors[8] = {
3164 0xFF4370FF,
3165 0xFF26A7FF,
3166 0xFF58EEFF,
3167 0xFF57E1D4,
3168 0xFF65CC9C,
3169 0xFF6ABB66,
3170 0xFFF5A542,
3171 0xFFC2577E,
3172 };
3173 dst = _sapp.default_icon_pixels;
3174 const uint32_t blank = 0x00FFFFFF;
3175 const uint32_t shadow = 0xFF000000;
3176 for (int i = 0; i < num_icons; i++) {
3177 const int dim = icon_sizes[i];
3178 SOKOL_ASSERT((dim % 8) == 0);
3179 const int scale = dim / 8;
3180 for (int ty = 0, y = 0; ty < 8; ty++) {
3181 const uint32_t color = colors[ty];
3182 for (int sy = 0; sy < scale; sy++, y++) {
3183 uint8_t bits = tile[ty];
3184 for (int tx = 0, x = 0; tx < 8; tx++, bits<<=1) {
3185 uint32_t pixel = (0 == (bits & 0x80)) ? blank : color;
3186 for (int sx = 0; sx < scale; sx++, x++) {
3187 SOKOL_ASSERT(dst < dst_end);
3188 *dst++ = pixel;
3189 }
3190 }
3191 }
3192 }
3193 }
3194 SOKOL_ASSERT(dst == dst_end);
3195
3196 // right shadow
3197 dst = _sapp.default_icon_pixels;
3198 for (int i = 0; i < num_icons; i++) {
3199 const int dim = icon_sizes[i];
3200 for (int y = 0; y < dim; y++) {
3201 uint32_t prev_color = blank;
3202 for (int x = 0; x < dim; x++) {
3203 const int dst_index = y * dim + x;
3204 const uint32_t cur_color = dst[dst_index];
3205 if ((cur_color == blank) && (prev_color != blank)) {
3206 dst[dst_index] = shadow;
3207 }
3208 prev_color = cur_color;
3209 }
3210 }
3211 dst += dim * dim;
3212 }
3213 SOKOL_ASSERT(dst == dst_end);
3214
3215 // bottom shadow
3216 dst = _sapp.default_icon_pixels;
3217 for (int i = 0; i < num_icons; i++) {
3218 const int dim = icon_sizes[i];
3219 for (int x = 0; x < dim; x++) {
3220 uint32_t prev_color = blank;
3221 for (int y = 0; y < dim; y++) {
3222 const int dst_index = y * dim + x;
3223 const uint32_t cur_color = dst[dst_index];
3224 if ((cur_color == blank) && (prev_color != blank)) {
3225 dst[dst_index] = shadow;
3226 }
3227 prev_color = cur_color;
3228 }
3229 }
3230 dst += dim * dim;
3231 }
3232 SOKOL_ASSERT(dst == dst_end);
3233}
3234
3235/*== MacOS/iOS ===============================================================*/
3236#if defined(_SAPP_APPLE)
3237
3238#if __has_feature(objc_arc)
3239#define _SAPP_OBJC_RELEASE(obj) { obj = nil; }
3240#else
3241#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
3242#endif
3243
3244/*== MacOS ===================================================================*/
3245#if defined(_SAPP_MACOS)
3246
3247_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) {
3248 _sapp.keycodes[0x1D] = SAPP_KEYCODE_0;
3249 _sapp.keycodes[0x12] = SAPP_KEYCODE_1;
3250 _sapp.keycodes[0x13] = SAPP_KEYCODE_2;
3251 _sapp.keycodes[0x14] = SAPP_KEYCODE_3;
3252 _sapp.keycodes[0x15] = SAPP_KEYCODE_4;
3253 _sapp.keycodes[0x17] = SAPP_KEYCODE_5;
3254 _sapp.keycodes[0x16] = SAPP_KEYCODE_6;
3255 _sapp.keycodes[0x1A] = SAPP_KEYCODE_7;
3256 _sapp.keycodes[0x1C] = SAPP_KEYCODE_8;
3257 _sapp.keycodes[0x19] = SAPP_KEYCODE_9;
3258 _sapp.keycodes[0x00] = SAPP_KEYCODE_A;
3259 _sapp.keycodes[0x0B] = SAPP_KEYCODE_B;
3260 _sapp.keycodes[0x08] = SAPP_KEYCODE_C;
3261 _sapp.keycodes[0x02] = SAPP_KEYCODE_D;
3262 _sapp.keycodes[0x0E] = SAPP_KEYCODE_E;
3263 _sapp.keycodes[0x03] = SAPP_KEYCODE_F;
3264 _sapp.keycodes[0x05] = SAPP_KEYCODE_G;
3265 _sapp.keycodes[0x04] = SAPP_KEYCODE_H;
3266 _sapp.keycodes[0x22] = SAPP_KEYCODE_I;
3267 _sapp.keycodes[0x26] = SAPP_KEYCODE_J;
3268 _sapp.keycodes[0x28] = SAPP_KEYCODE_K;
3269 _sapp.keycodes[0x25] = SAPP_KEYCODE_L;
3270 _sapp.keycodes[0x2E] = SAPP_KEYCODE_M;
3271 _sapp.keycodes[0x2D] = SAPP_KEYCODE_N;
3272 _sapp.keycodes[0x1F] = SAPP_KEYCODE_O;
3273 _sapp.keycodes[0x23] = SAPP_KEYCODE_P;
3274 _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q;
3275 _sapp.keycodes[0x0F] = SAPP_KEYCODE_R;
3276 _sapp.keycodes[0x01] = SAPP_KEYCODE_S;
3277 _sapp.keycodes[0x11] = SAPP_KEYCODE_T;
3278 _sapp.keycodes[0x20] = SAPP_KEYCODE_U;
3279 _sapp.keycodes[0x09] = SAPP_KEYCODE_V;
3280 _sapp.keycodes[0x0D] = SAPP_KEYCODE_W;
3281 _sapp.keycodes[0x07] = SAPP_KEYCODE_X;
3282 _sapp.keycodes[0x10] = SAPP_KEYCODE_Y;
3283 _sapp.keycodes[0x06] = SAPP_KEYCODE_Z;
3284 _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE;
3285 _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH;
3286 _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA;
3287 _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL;
3288 _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT;
3289 _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET;
3290 _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS;
3291 _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD;
3292 _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET;
3293 _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON;
3294 _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH;
3295 _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1;
3296 _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE;
3297 _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK;
3298 _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE;
3299 _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN;
3300 _sapp.keycodes[0x77] = SAPP_KEYCODE_END;
3301 _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER;
3302 _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE;
3303 _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1;
3304 _sapp.keycodes[0x78] = SAPP_KEYCODE_F2;
3305 _sapp.keycodes[0x63] = SAPP_KEYCODE_F3;
3306 _sapp.keycodes[0x76] = SAPP_KEYCODE_F4;
3307 _sapp.keycodes[0x60] = SAPP_KEYCODE_F5;
3308 _sapp.keycodes[0x61] = SAPP_KEYCODE_F6;
3309 _sapp.keycodes[0x62] = SAPP_KEYCODE_F7;
3310 _sapp.keycodes[0x64] = SAPP_KEYCODE_F8;
3311 _sapp.keycodes[0x65] = SAPP_KEYCODE_F9;
3312 _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10;
3313 _sapp.keycodes[0x67] = SAPP_KEYCODE_F11;
3314 _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12;
3315 _sapp.keycodes[0x69] = SAPP_KEYCODE_F13;
3316 _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14;
3317 _sapp.keycodes[0x71] = SAPP_KEYCODE_F15;
3318 _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16;
3319 _sapp.keycodes[0x40] = SAPP_KEYCODE_F17;
3320 _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18;
3321 _sapp.keycodes[0x50] = SAPP_KEYCODE_F19;
3322 _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20;
3323 _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME;
3324 _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT;
3325 _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT;
3326 _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT;
3327 _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL;
3328 _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT;
3329 _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER;
3330 _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU;
3331 _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK;
3332 _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN;
3333 _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP;
3334 _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT;
3335 _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT;
3336 _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL;
3337 _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT;
3338 _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER;
3339 _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE;
3340 _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB;
3341 _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP;
3342 _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0;
3343 _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1;
3344 _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2;
3345 _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3;
3346 _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4;
3347 _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5;
3348 _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6;
3349 _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7;
3350 _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8;
3351 _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9;
3352 _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD;
3353 _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL;
3354 _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE;
3355 _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER;
3356 _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL;
3357 _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY;
3358 _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT;
3359}
3360
3361_SOKOL_PRIVATE void _sapp_macos_discard_state(void) {
3362 // NOTE: it's safe to call [release] on a nil object
3363 _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area);
3364 _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg);
3365 _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg);
3366 _SAPP_OBJC_RELEASE(_sapp.macos.view);
3367 #if defined(SOKOL_METAL)
3368 _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device);
3369 #endif
3370 _SAPP_OBJC_RELEASE(_sapp.macos.window);
3371}
3372
3373// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm)
3374@interface NSCursor()
3375+ (id)_windowResizeNorthWestSouthEastCursor;
3376+ (id)_windowResizeNorthEastSouthWestCursor;
3377+ (id)_windowResizeNorthSouthCursor;
3378+ (id)_windowResizeEastWestCursor;
3379@end
3380
3381_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) {
3382 _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug
3383 _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor];
3384 _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor];
3385 _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor];
3386 _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor];
3387 _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
3388 _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
3389 _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
3390 _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
3391 _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor];
3392 _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor];
3393}
3394
3395_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) {
3396 _sapp_init_state(desc);
3397 _sapp_macos_init_keytable();
3398 [NSApplication sharedApplication];
3399 // set the application dock icon as early as possible, otherwise
3400 // the dummy icon will be visible for a short time
3401 sapp_set_icon(&_sapp.desc.icon);
3402 _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init];
3403 NSApp.delegate = _sapp.macos.app_dlg;
3404 [NSApp run];
3405 // NOTE: [NSApp run] never returns, instead cleanup code
3406 // must be put into applicationWillTerminate
3407}
3408
3409/* MacOS entry function */
3410#if !defined(SOKOL_NO_ENTRY)
3411int main(int argc, char* argv[]) {
3412 sapp_desc desc = sokol_main(argc, argv);
3413 _sapp_macos_run(&desc);
3414 return 0;
3415}
3416#endif /* SOKOL_NO_ENTRY */
3417
3418_SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) {
3419 const NSEventModifierFlags f = ev.modifierFlags;
3420 const NSUInteger b = NSEvent.pressedMouseButtons;
3421 uint32_t m = 0;
3422 if (f & NSEventModifierFlagShift) {
3423 m |= SAPP_MODIFIER_SHIFT;
3424 }
3425 if (f & NSEventModifierFlagControl) {
3426 m |= SAPP_MODIFIER_CTRL;
3427 }
3428 if (f & NSEventModifierFlagOption) {
3429 m |= SAPP_MODIFIER_ALT;
3430 }
3431 if (f & NSEventModifierFlagCommand) {
3432 m |= SAPP_MODIFIER_SUPER;
3433 }
3434 if (0 != (b & (1<<0))) {
3435 m |= SAPP_MODIFIER_LMB;
3436 }
3437 if (0 != (b & (1<<1))) {
3438 m |= SAPP_MODIFIER_RMB;
3439 }
3440 if (0 != (b & (1<<2))) {
3441 m |= SAPP_MODIFIER_MMB;
3442 }
3443 return m;
3444}
3445
3446_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) {
3447 if (_sapp_events_enabled()) {
3448 _sapp_init_event(type);
3449 _sapp.event.mouse_button = btn;
3450 _sapp.event.modifiers = mod;
3451 _sapp_call_event(&_sapp.event);
3452 }
3453}
3454
3455_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) {
3456 if (_sapp_events_enabled()) {
3457 _sapp_init_event(type);
3458 _sapp.event.key_code = key;
3459 _sapp.event.key_repeat = repeat;
3460 _sapp.event.modifiers = mod;
3461 _sapp_call_event(&_sapp.event);
3462 }
3463}
3464
3465_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) {
3466 if (_sapp_events_enabled()) {
3467 _sapp_init_event(type);
3468 _sapp_call_event(&_sapp.event);
3469 }
3470}
3471
3472/* NOTE: unlike the iOS version of this function, the macOS version
3473 can dynamically update the DPI scaling factor when a window is moved
3474 between HighDPI / LowDPI screens.
3475*/
3476_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) {
3477 if (_sapp.desc.high_dpi) {
3478 _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor;
3479 }
3480 else {
3481 _sapp.dpi_scale = 1.0f;
3482 }
3483 const NSRect bounds = [_sapp.macos.view bounds];
3484 _sapp.window_width = (int)roundf(bounds.size.width);
3485 _sapp.window_height = (int)roundf(bounds.size.height);
3486 #if defined(SOKOL_METAL)
3487 _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale);
3488 _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale);
3489 const CGSize fb_size = _sapp.macos.view.drawableSize;
3490 const int cur_fb_width = (int)roundf(fb_size.width);
3491 const int cur_fb_height = (int)roundf(fb_size.height);
3492 const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
3493 (_sapp.framebuffer_height != cur_fb_height);
3494 #elif defined(SOKOL_GLCORE33)
3495 const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale);
3496 const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale);
3497 const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
3498 (_sapp.framebuffer_height != cur_fb_height);
3499 _sapp.framebuffer_width = cur_fb_width;
3500 _sapp.framebuffer_height = cur_fb_height;
3501 #endif
3502 if (_sapp.framebuffer_width == 0) {
3503 _sapp.framebuffer_width = 1;
3504 }
3505 if (_sapp.framebuffer_height == 0) {
3506 _sapp.framebuffer_height = 1;
3507 }
3508 if (_sapp.window_width == 0) {
3509 _sapp.window_width = 1;
3510 }
3511 if (_sapp.window_height == 0) {
3512 _sapp.window_height = 1;
3513 }
3514 if (dim_changed) {
3515 #if defined(SOKOL_METAL)
3516 CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height };
3517 _sapp.macos.view.drawableSize = drawable_size;
3518 #else
3519 // nothing to do for GL?
3520 #endif
3521 if (!_sapp.first_frame) {
3522 _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED);
3523 }
3524 }
3525}
3526
3527_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) {
3528 /* NOTE: the _sapp.fullscreen flag is also notified by the
3529 windowDidEnterFullscreen / windowDidExitFullscreen
3530 event handlers
3531 */
3532 _sapp.fullscreen = !_sapp.fullscreen;
3533 [_sapp.macos.window toggleFullScreen:nil];
3534}
3535
3536_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) {
3537 @autoreleasepool {
3538 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3539 [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
3540 [pasteboard setString:@(str) forType:NSPasteboardTypeString];
3541 }
3542}
3543
3544_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) {
3545 SOKOL_ASSERT(_sapp.clipboard.buffer);
3546 @autoreleasepool {
3547 _sapp.clipboard.buffer[0] = 0;
3548 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3549 if (![[pasteboard types] containsObject:NSPasteboardTypeString]) {
3550 return _sapp.clipboard.buffer;
3551 }
3552 NSString* str = [pasteboard stringForType:NSPasteboardTypeString];
3553 if (!str) {
3554 return _sapp.clipboard.buffer;
3555 }
3556 _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size);
3557 }
3558 return _sapp.clipboard.buffer;
3559}
3560
3561_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) {
3562 [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]];
3563}
3564
3565_SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) {
3566 if (!_sapp.mouse.locked) {
3567 const NSPoint mouse_pos = event.locationInWindow;
3568 float new_x = mouse_pos.x * _sapp.dpi_scale;
3569 float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1;
3570 /* don't update dx/dy in the very first update */
3571 if (_sapp.mouse.pos_valid) {
3572 _sapp.mouse.dx = new_x - _sapp.mouse.x;
3573 _sapp.mouse.dy = new_y - _sapp.mouse.y;
3574 }
3575 _sapp.mouse.x = new_x;
3576 _sapp.mouse.y = new_y;
3577 _sapp.mouse.pos_valid = true;
3578 }
3579}
3580
3581_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) {
3582 /* NOTE: this function is only called when the mouse visibility actually changes */
3583 if (visible) {
3584 CGDisplayShowCursor(kCGDirectMainDisplay);
3585 }
3586 else {
3587 CGDisplayHideCursor(kCGDirectMainDisplay);
3588 }
3589}
3590
3591_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) {
3592 if (lock == _sapp.mouse.locked) {
3593 return;
3594 }
3595 _sapp.mouse.dx = 0.0f;
3596 _sapp.mouse.dy = 0.0f;
3597 _sapp.mouse.locked = lock;
3598 /*
3599 NOTE that this code doesn't warp the mouse cursor to the window
3600 center as everybody else does it. This lead to a spike in the
3601 *second* mouse-moved event after the warp happened. The
3602 mouse centering doesn't seem to be required (mouse-moved events
3603 are reported correctly even when the cursor is at an edge of the screen).
3604
3605 NOTE also that the hide/show of the mouse cursor should properly
3606 stack with calls to sapp_show_mouse()
3607 */
3608 if (_sapp.mouse.locked) {
3609 CGAssociateMouseAndMouseCursorPosition(NO);
3610 [NSCursor hide];
3611 }
3612 else {
3613 [NSCursor unhide];
3614 CGAssociateMouseAndMouseCursorPosition(YES);
3615 }
3616}
3617
3618_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) {
3619 // show/hide cursor only if visibility status has changed (required because show/hide stacks)
3620 if (shown != _sapp.mouse.shown) {
3621 if (shown) {
3622 [NSCursor unhide];
3623 }
3624 else {
3625 [NSCursor hide];
3626 }
3627 }
3628 // update cursor type
3629 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
3630 if (_sapp.macos.cursors[cursor]) {
3631 [_sapp.macos.cursors[cursor] set];
3632 }
3633 else {
3634 [[NSCursor arrowCursor] set];
3635 }
3636}
3637
3638_SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) {
3639 NSDockTile* dock_tile = NSApp.dockTile;
3640 const int wanted_width = (int) dock_tile.size.width;
3641 const int wanted_height = (int) dock_tile.size.height;
3642 const int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, wanted_width, wanted_height);
3643 const sapp_image_desc* img_desc = &icon_desc->images[img_index];
3644
3645 CGColorSpaceRef cg_color_space = CGColorSpaceCreateDeviceRGB();
3646 CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)img_desc->pixels.ptr, (CFIndex)img_desc->pixels.size);
3647 CGDataProviderRef cg_data_provider = CGDataProviderCreateWithCFData(cf_data);
3648 CGImageRef cg_img = CGImageCreate(
3649 (size_t)img_desc->width, // width
3650 (size_t)img_desc->height, // height
3651 8, // bitsPerComponent
3652 32, // bitsPerPixel
3653 (size_t)img_desc->width * 4,// bytesPerRow
3654 cg_color_space, // space
3655 kCGImageAlphaLast | kCGImageByteOrderDefault, // bitmapInfo
3656 cg_data_provider, // provider
3657 NULL, // decode
3658 false, // shouldInterpolate
3659 kCGRenderingIntentDefault);
3660 CFRelease(cf_data);
3661 CGDataProviderRelease(cg_data_provider);
3662 CGColorSpaceRelease(cg_color_space);
3663
3664 NSImage* ns_image = [[NSImage alloc] initWithCGImage:cg_img size:dock_tile.size];
3665 dock_tile.contentView = [NSImageView imageViewWithImage:ns_image];
3666 [dock_tile display];
3667 _SAPP_OBJC_RELEASE(ns_image);
3668 CGImageRelease(cg_img);
3669}
3670
3671_SOKOL_PRIVATE void _sapp_macos_frame(void) {
3672 _sapp_frame();
3673 if (_sapp.quit_requested || _sapp.quit_ordered) {
3674 [_sapp.macos.window performClose:nil];
3675 }
3676}
3677
3678// __v_ start
3679#include "sokol_app2.h" // __v_
3680// __v_ end
3681
3682@implementation _sapp_macos_app_delegate
3683- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
3684 _SOKOL_UNUSED(aNotification);
3685 _sapp_macos_init_cursors();
3686 if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) {
3687 // use 4/5 of screen size as default size
3688 NSRect screen_rect = NSScreen.mainScreen.frame;
3689 if (_sapp.window_width == 0) {
3690 _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f);
3691 }
3692 if (_sapp.window_height == 0) {
3693 _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f);
3694 }
3695 }
3696 const NSUInteger style =
3697 // __v_ start
3698 _sapp.desc.fullscreen ? NSWindowStyleMaskBorderless : // __v_
3699 // __v_ end
3700 NSWindowStyleMaskTitled |
3701 NSWindowStyleMaskClosable |
3702 NSWindowStyleMaskMiniaturizable |
3703 NSWindowStyleMaskResizable;
3704 NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height);
3705 _sapp.macos.window = [[_sapp_macos_window alloc]
3706 initWithContentRect:window_rect
3707 styleMask:style
3708 backing:NSBackingStoreBuffered
3709 defer:NO];
3710 _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate
3711 _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title];
3712 _sapp.macos.window.acceptsMouseMovedEvents = YES;
3713 _sapp.macos.window.restorable = YES;
3714
3715 // __v_ start
3716 // Quit menu
3717 NSMenu* menu_bar = [[NSMenu alloc] init];
3718 NSMenuItem* app_menu_item = [[NSMenuItem alloc] init];
3719 [menu_bar addItem:app_menu_item];
3720 NSApp.mainMenu = menu_bar;
3721 NSMenu* app_menu = [[NSMenu alloc] init];
3722 NSString* window_title_as_nsstring = [NSString stringWithUTF8String:_sapp.window_title];
3723 // `quit_title` memory will be owned by the NSMenuItem, so no need to release it ourselves
3724 NSString* quit_title = [@"Quit " stringByAppendingString:window_title_as_nsstring];
3725 NSMenuItem* quit_menu_item = [[NSMenuItem alloc]
3726 initWithTitle:quit_title
3727 action:@selector(terminate:)
3728 keyEquivalent:@"q"];
3729 [app_menu addItem:quit_menu_item];
3730 app_menu_item.submenu = app_menu;
3731 _SAPP_OBJC_RELEASE( window_title_as_nsstring );
3732 _SAPP_OBJC_RELEASE( app_menu );
3733 _SAPP_OBJC_RELEASE( app_menu_item );
3734 _SAPP_OBJC_RELEASE( menu_bar );
3735 // __v_ end
3736
3737 _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init];
3738 _sapp.macos.window.delegate = _sapp.macos.win_dlg;
3739 #if defined(SOKOL_METAL)
3740 NSInteger max_fps = 60;
3741 #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000)
3742 if (@available(macOS 12.0, *)) {
3743 max_fps = [NSScreen.mainScreen maximumFramesPerSecond];
3744 }
3745 #endif
3746 _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice();
3747 _sapp.macos.view = [[_sapp_macos_view alloc] init];
3748 [_sapp.macos.view updateTrackingAreas];
3749 _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval;
3750 _sapp.macos.view.device = _sapp.macos.mtl_device;
3751 _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
3752 _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
3753 _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count;
3754 _sapp.macos.view.autoResizeDrawable = false;
3755 _sapp.macos.window.contentView = _sapp.macos.view;
3756 [_sapp.macos.window makeFirstResponder:_sapp.macos.view];
3757 _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest;
3758 #elif defined(SOKOL_GLCORE33)
3759 NSOpenGLPixelFormatAttribute attrs[32];
3760 int i = 0;
3761 attrs[i++] = NSOpenGLPFAAccelerated;
3762 attrs[i++] = NSOpenGLPFADoubleBuffer;
3763 attrs[i++] = NSOpenGLPFAOpenGLProfile;
3764 const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version;
3765 switch(glVersion) {
3766 case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break;
3767 case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break;
3768 case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break;
3769 default:
3770 _sapp_fail("Invalid NSOpenGLProfile (valid choices are 1.0, 3.2, and 4.1)\n");
3771 }
3772 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
3773 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
3774 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24;
3775 attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8;
3776 if (_sapp.sample_count > 1) {
3777 attrs[i++] = NSOpenGLPFAMultisample;
3778 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
3779 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count;
3780 }
3781 else {
3782 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0;
3783 }
3784 attrs[i++] = 0;
3785 NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
3786 SOKOL_ASSERT(glpixelformat_obj != nil);
3787
3788 _sapp.macos.view = [[_sapp_macos_view alloc]
3789 initWithFrame:window_rect
3790 pixelFormat:glpixelformat_obj];
3791 _SAPP_OBJC_RELEASE(glpixelformat_obj);
3792 [_sapp.macos.view updateTrackingAreas];
3793 if (_sapp.desc.high_dpi) {
3794 [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES];
3795 }
3796 else {
3797 [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO];
3798 }
3799
3800 _sapp.macos.window.contentView = _sapp.macos.view;
3801 [_sapp.macos.window makeFirstResponder:_sapp.macos.view];
3802
3803 NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001
3804 target:_sapp.macos.view
3805 selector:@selector(timerFired:)
3806 userInfo:nil
3807 repeats:YES];
3808 [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode];
3809 timer_obj = nil;
3810 #endif
3811 [_sapp.macos.window center];
3812 _sapp.valid = true;
3813
3814
3815 // __v_ start
3816 if (!_sapp.__v_native_render) { // __v_
3817
3818 if (_sapp.fullscreen) {
3819 /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */
3820 [_sapp.macos.window toggleFullScreen:self];
3821 }
3822
3823 else {
3824 [_sapp.macos.window center];
3825 }
3826 } // __v_
3827 // __v_ end
3828
3829
3830 NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
3831 [NSApp activateIgnoringOtherApps:YES];
3832
3833
3834
3835 // __v start
3836 ///////////////////////////////////////////////////////
3837 // Create a child view for native rendering
3838 if (_sapp.__v_native_render) {
3839
3840 CGRect wRect = _sapp.macos.window.frame;
3841 NSView *contentView =_sapp.macos.window.contentView;
3842 CGRect cRect = contentView.frame;
3843
3844 CGRect rect = CGRectMake(wRect.origin.x, wRect.origin.y, cRect.size.width, cRect.size.height);
3845 NSWindow *overlayWindow = [[NSWindow alloc]initWithContentRect:rect
3846 styleMask:NSBorderlessWindowMask
3847 backing:NSBackingStoreBuffered
3848 defer:NO];
3849 //overlayWindow.backgroundColor = [NSColor whiteColor];
3850
3851 //overlayWindow.backgroundColor = [[NSColor whiteColor] colorWithAlphaComponent:0];
3852 [overlayWindow setOpaque:YES];
3853 [_sapp.macos.window setIgnoresMouseEvents:NO];
3854 g_view = [[MyView2 alloc] init];
3855 overlayWindow.contentView = g_view;
3856
3857 [ contentView addSubview:g_view];
3858 //[ _sapp.macos.window addChildWindow:overlayWindow ordered:NSWindowAbove];
3859 [_sapp.macos.window center];
3860 }
3861 //////////////////////////////////
3862 // __v_ end
3863
3864
3865
3866 [_sapp.macos.window makeKeyAndOrderFront:nil];
3867 _sapp_macos_update_dimensions();
3868
3869 // __v_ start
3870 // [NSEvent setMouseCoalescingEnabled:NO];
3871 // __v_ end
3872
3873}
3874
3875- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender {
3876 _SOKOL_UNUSED(sender);
3877 return YES;
3878}
3879
3880- (void)applicationWillTerminate:(NSNotification*)notification {
3881 _SOKOL_UNUSED(notification);
3882 _sapp_call_cleanup();
3883 _sapp_macos_discard_state();
3884 _sapp_discard_state();
3885}
3886@end
3887
3888@implementation _sapp_macos_window_delegate
3889- (BOOL)windowShouldClose:(id)sender {
3890 _SOKOL_UNUSED(sender);
3891 /* only give user-code a chance to intervene when sapp_quit() wasn't already called */
3892 if (!_sapp.quit_ordered) {
3893 /* if window should be closed and event handling is enabled, give user code
3894 a chance to intervene via sapp_cancel_quit()
3895 */
3896 _sapp.quit_requested = true;
3897 _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED);
3898 /* user code hasn't intervened, quit the app */
3899 if (_sapp.quit_requested) {
3900 _sapp.quit_ordered = true;
3901 }
3902 }
3903 if (_sapp.quit_ordered) {
3904 return YES;
3905 }
3906 else {
3907 return NO;
3908 }
3909}
3910
3911- (void)windowDidResize:(NSNotification*)notification {
3912 _SOKOL_UNUSED(notification);
3913 _sapp_macos_update_dimensions();
3914}
3915
3916- (void)windowDidChangeScreen:(NSNotification*)notification {
3917 _SOKOL_UNUSED(notification);
3918 _sapp_timing_reset(&_sapp.timing);
3919 _sapp_macos_update_dimensions();
3920}
3921
3922- (void)windowDidMiniaturize:(NSNotification*)notification {
3923 _SOKOL_UNUSED(notification);
3924 _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED);
3925}
3926
3927- (void)windowDidDeminiaturize:(NSNotification*)notification {
3928 _SOKOL_UNUSED(notification);
3929 _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED);
3930}
3931
3932- (void)windowDidBecomeKey:(NSNotification*)notification {
3933 _SOKOL_UNUSED(notification);
3934 _sapp_macos_app_event(SAPP_EVENTTYPE_FOCUSED);
3935}
3936
3937- (void)windowDidResignKey:(NSNotification*)notification {
3938 _SOKOL_UNUSED(notification);
3939 _sapp_macos_app_event(SAPP_EVENTTYPE_UNFOCUSED);
3940}
3941
3942- (void)windowDidEnterFullScreen:(NSNotification*)notification {
3943 _SOKOL_UNUSED(notification);
3944 _sapp.fullscreen = true;
3945}
3946
3947- (void)windowDidExitFullScreen:(NSNotification*)notification {
3948 _SOKOL_UNUSED(notification);
3949 _sapp.fullscreen = false;
3950}
3951@end
3952
3953@implementation _sapp_macos_window
3954
3955// __v_ start
3956- (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless
3957- (BOOL)canBecomeMainWindow { return YES; }
3958// __v_ end
3959
3960- (instancetype)initWithContentRect:(NSRect)contentRect
3961 styleMask:(NSWindowStyleMask)style
3962 backing:(NSBackingStoreType)backingStoreType
3963 defer:(BOOL)flag {
3964 if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) {
3965 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
3966 [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
3967 #endif
3968 }
3969 return self;
3970}
3971
3972- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
3973 return NSDragOperationCopy;
3974}
3975
3976- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
3977 return NSDragOperationCopy;
3978}
3979
3980- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
3981 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
3982 NSPasteboard *pboard = [sender draggingPasteboard];
3983 if ([pboard.types containsObject:NSPasteboardTypeFileURL]) {
3984 _sapp_clear_drop_buffer();
3985 _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count;
3986 bool drop_failed = false;
3987 for (int i = 0; i < _sapp.drop.num_files; i++) {
3988 NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]];
3989 if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) {
3990 SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
3991 drop_failed = true;
3992 break;
3993 }
3994 }
3995 if (!drop_failed) {
3996 if (_sapp_events_enabled()) {
3997 _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED);
3998 _sapp_call_event(&_sapp.event);
3999 }
4000 }
4001 else {
4002 _sapp_clear_drop_buffer();
4003 _sapp.drop.num_files = 0;
4004 }
4005 return YES;
4006 }
4007 #endif
4008 return NO;
4009}
4010@end
4011
4012@implementation _sapp_macos_view
4013#if defined(SOKOL_GLCORE33)
4014/* NOTE: this is a hack/fix when the initial window size has been clipped by
4015 macOS because it didn't fit on the screen, in that case the
4016 frame size of the window is reported wrong if low-dpi rendering
4017 was requested (instead the high-dpi dimensions are returned)
4018 until the window is resized for the first time.
4019
4020 Hooking into reshape and getting the frame dimensions seems to report
4021 the correct dimensions.
4022*/
4023- (void)reshape {
4024 _sapp_macos_update_dimensions();
4025 [super reshape];
4026}
4027- (void)timerFired:(id)sender {
4028 _SOKOL_UNUSED(sender);
4029 [self setNeedsDisplay:YES];
4030}
4031- (void)prepareOpenGL {
4032 [super prepareOpenGL];
4033 GLint swapInt = 1;
4034 NSOpenGLContext* ctx = [_sapp.macos.view openGLContext];
4035 [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval];
4036 [ctx makeCurrentContext];
4037}
4038#endif
4039
4040_SOKOL_PRIVATE void _sapp_macos_poll_input_events() {
4041 /*
4042
4043 NOTE: late event polling temporarily out-commented to check if this
4044 causes infrequent and almost impossible to reproduce probelms with the
4045 window close events, see:
4046 https://github.com/floooh/sokol/pull/483#issuecomment-805148815
4047
4048
4049 const NSEventMask mask = NSEventMaskLeftMouseDown |
4050 NSEventMaskLeftMouseUp|
4051 NSEventMaskRightMouseDown |
4052 NSEventMaskRightMouseUp |
4053 NSEventMaskMouseMoved |
4054 NSEventMaskLeftMouseDragged |
4055 NSEventMaskRightMouseDragged |
4056 NSEventMaskMouseEntered |
4057 NSEventMaskMouseExited |
4058 NSEventMaskKeyDown |
4059 NSEventMaskKeyUp |
4060 NSEventMaskCursorUpdate |
4061 NSEventMaskScrollWheel |
4062 NSEventMaskTabletPoint |
4063 NSEventMaskTabletProximity |
4064 NSEventMaskOtherMouseDown |
4065 NSEventMaskOtherMouseUp |
4066 NSEventMaskOtherMouseDragged |
4067 NSEventMaskPressure |
4068 NSEventMaskDirectTouch;
4069 @autoreleasepool {
4070 for (;;) {
4071 // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend,
4072 // see: https://github.com/floooh/sokol/issues/486
4073 NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
4074 if (event == nil) {
4075 break;
4076 }
4077 [NSApp sendEvent:event];
4078 }
4079 }
4080 */
4081}
4082
4083- (void)drawRect:(NSRect)rect {
4084 _SOKOL_UNUSED(rect);
4085 _sapp_timing_measure(&_sapp.timing);
4086 /* Catch any last-moment input events */
4087 _sapp_macos_poll_input_events();
4088 @autoreleasepool {
4089 _sapp_macos_frame();
4090 }
4091 #if !defined(SOKOL_METAL)
4092 [[_sapp.macos.view openGLContext] flushBuffer];
4093 #endif
4094}
4095
4096- (BOOL)isOpaque {
4097 return YES;
4098}
4099- (BOOL)canBecomeKeyView {
4100 return YES;
4101}
4102- (BOOL)acceptsFirstResponder {
4103 return YES;
4104}
4105- (void)updateTrackingAreas {
4106 if (_sapp.macos.tracking_area != nil) {
4107 [self removeTrackingArea:_sapp.macos.tracking_area];
4108 _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area);
4109 }
4110 const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
4111 NSTrackingActiveInKeyWindow |
4112 NSTrackingEnabledDuringMouseDrag |
4113 NSTrackingCursorUpdate |
4114 NSTrackingInVisibleRect |
4115 NSTrackingAssumeInside;
4116 _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
4117 [self addTrackingArea:_sapp.macos.tracking_area];
4118 [super updateTrackingAreas];
4119}
4120- (void)mouseEntered:(NSEvent*)event {
4121 _sapp_macos_update_mouse(event);
4122 /* don't send mouse enter/leave while dragging (so that it behaves the same as
4123 on Windows while SetCapture is active
4124 */
4125 if (0 == _sapp.macos.mouse_buttons) {
4126 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event));
4127 }
4128}
4129- (void)mouseExited:(NSEvent*)event {
4130 _sapp_macos_update_mouse(event);
4131 if (0 == _sapp.macos.mouse_buttons) {
4132 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event));
4133 }
4134}
4135- (void)mouseDown:(NSEvent*)event {
4136 _sapp_macos_update_mouse(event);
4137 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event));
4138 _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_LEFT);
4139}
4140- (void)mouseUp:(NSEvent*)event {
4141 _sapp_macos_update_mouse(event);
4142 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event));
4143 _sapp.macos.mouse_buttons &= ~(1<<SAPP_MOUSEBUTTON_LEFT);
4144}
4145- (void)rightMouseDown:(NSEvent*)event {
4146 _sapp_macos_update_mouse(event);
4147 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_RIGHT, _sapp_macos_mods(event));
4148 _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_RIGHT);
4149}
4150- (void)rightMouseUp:(NSEvent*)event {
4151 _sapp_macos_update_mouse(event);
4152 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_RIGHT, _sapp_macos_mods(event));
4153 _sapp.macos.mouse_buttons &= ~(1<<SAPP_MOUSEBUTTON_RIGHT);
4154}
4155- (void)otherMouseDown:(NSEvent*)event {
4156 _sapp_macos_update_mouse(event);
4157 if (2 == event.buttonNumber) {
4158 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_MIDDLE, _sapp_macos_mods(event));
4159 _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_MIDDLE);
4160 }
4161}
4162- (void)otherMouseUp:(NSEvent*)event {
4163 _sapp_macos_update_mouse(event);
4164 if (2 == event.buttonNumber) {
4165 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_MIDDLE, _sapp_macos_mods(event));
4166 _sapp.macos.mouse_buttons &= (1<<SAPP_MOUSEBUTTON_MIDDLE);
4167 }
4168}
4169- (void)otherMouseDragged:(NSEvent*)event {
4170 _sapp_macos_update_mouse(event);
4171 if (2 == event.buttonNumber) {
4172 if (_sapp.mouse.locked) {
4173 _sapp.mouse.dx = [event deltaX];
4174 _sapp.mouse.dy = [event deltaY];
4175 }
4176 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event));
4177 }
4178}
4179- (void)mouseMoved:(NSEvent*)event {
4180 _sapp_macos_update_mouse(event);
4181 if (_sapp.mouse.locked) {
4182 _sapp.mouse.dx = [event deltaX];
4183 _sapp.mouse.dy = [event deltaY];
4184 }
4185 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID , _sapp_macos_mods(event));
4186}
4187- (void)mouseDragged:(NSEvent*)event {
4188 _sapp_macos_update_mouse(event);
4189 if (_sapp.mouse.locked) {
4190 _sapp.mouse.dx = [event deltaX];
4191 _sapp.mouse.dy = [event deltaY];
4192 }
4193 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID , _sapp_macos_mods(event));
4194}
4195- (void)rightMouseDragged:(NSEvent*)event {
4196 _sapp_macos_update_mouse(event);
4197 if (_sapp.mouse.locked) {
4198 _sapp.mouse.dx = [event deltaX];
4199 _sapp.mouse.dy = [event deltaY];
4200 }
4201 _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event));
4202}
4203- (void)scrollWheel:(NSEvent*)event {
4204 _sapp_macos_update_mouse(event);
4205 if (_sapp_events_enabled()) {
4206 float dx = (float) event.scrollingDeltaX;
4207 float dy = (float) event.scrollingDeltaY;
4208 if (event.hasPreciseScrollingDeltas) {
4209 dx *= 0.1;
4210 dy *= 0.1;
4211 }
4212 if ((_sapp_absf(dx) > 0.0f) || (_sapp_absf(dy) > 0.0f)) {
4213 _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL);
4214 _sapp.event.modifiers = _sapp_macos_mods(event);
4215 _sapp.event.scroll_x = dx;
4216 _sapp.event.scroll_y = dy;
4217 _sapp_call_event(&_sapp.event);
4218 }
4219 }
4220}
4221- (void)keyDown:(NSEvent*)event {
4222 if (_sapp_events_enabled()) {
4223 const uint32_t mods = _sapp_macos_mods(event);
4224 /* NOTE: macOS doesn't send keyUp events while the Cmd key is pressed,
4225 as a workaround, to prevent key presses from sticking we'll send
4226 a keyup event following right after the keydown if SUPER is also pressed
4227 */
4228 const sapp_keycode key_code = _sapp_translate_key(event.keyCode);
4229 _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods);
4230 if (0 != (mods & SAPP_MODIFIER_SUPER)) {
4231 _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods);
4232 }
4233 const NSString* chars = event.characters;
4234 const NSUInteger len = chars.length;
4235 if (len > 0) {
4236 _sapp_init_event(SAPP_EVENTTYPE_CHAR);
4237 _sapp.event.modifiers = mods;
4238 for (NSUInteger i = 0; i < len; i++) {
4239 const unichar codepoint = [chars characterAtIndex:i];
4240 if ((codepoint & 0xFF00) == 0xF700) {
4241 continue;
4242 }
4243 _sapp.event.char_code = codepoint;
4244 _sapp.event.key_repeat = event.isARepeat;
4245 _sapp_call_event(&_sapp.event);
4246 }
4247 }
4248 /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */
4249 if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) {
4250 _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
4251 _sapp_call_event(&_sapp.event);
4252 }
4253 }
4254}
4255- (void)keyUp:(NSEvent*)event {
4256 _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP,
4257 _sapp_translate_key(event.keyCode),
4258 event.isARepeat,
4259 _sapp_macos_mods(event));
4260}
4261- (void)flagsChanged:(NSEvent*)event {
4262 const uint32_t old_f = _sapp.macos.flags_changed_store;
4263 const uint32_t new_f = (uint32_t)event.modifierFlags;
4264 _sapp.macos.flags_changed_store = new_f;
4265 sapp_keycode key_code = SAPP_KEYCODE_INVALID;
4266 bool down = false;
4267 if ((new_f ^ old_f) & NSEventModifierFlagShift) {
4268 key_code = SAPP_KEYCODE_LEFT_SHIFT;
4269 down = 0 != (new_f & NSEventModifierFlagShift);
4270 }
4271 if ((new_f ^ old_f) & NSEventModifierFlagControl) {
4272 key_code = SAPP_KEYCODE_LEFT_CONTROL;
4273 down = 0 != (new_f & NSEventModifierFlagControl);
4274 }
4275 if ((new_f ^ old_f) & NSEventModifierFlagOption) {
4276 key_code = SAPP_KEYCODE_LEFT_ALT;
4277 down = 0 != (new_f & NSEventModifierFlagOption);
4278 }
4279 if ((new_f ^ old_f) & NSEventModifierFlagCommand) {
4280 key_code = SAPP_KEYCODE_LEFT_SUPER;
4281 down = 0 != (new_f & NSEventModifierFlagCommand);
4282 }
4283 if (key_code != SAPP_KEYCODE_INVALID) {
4284 _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP,
4285 key_code,
4286 false,
4287 _sapp_macos_mods(event));
4288 }
4289}
4290@end
4291
4292#endif /* MacOS */
4293
4294/*== iOS =====================================================================*/
4295#if defined(_SAPP_IOS)
4296
4297_SOKOL_PRIVATE void _sapp_ios_discard_state(void) {
4298 // NOTE: it's safe to call [release] on a nil object
4299 _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg);
4300 _SAPP_OBJC_RELEASE(_sapp.ios.textfield);
4301 #if defined(SOKOL_METAL)
4302 _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl);
4303 _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device);
4304 #else
4305 _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl);
4306 _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx);
4307 #endif
4308 _SAPP_OBJC_RELEASE(_sapp.ios.view);
4309 _SAPP_OBJC_RELEASE(_sapp.ios.window);
4310}
4311
4312_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) {
4313 _sapp_init_state(desc);
4314 static int argc = 1;
4315 static char* argv[] = { (char*)"sokol_app" };
4316 UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class]));
4317}
4318
4319/* iOS entry function */
4320#if !defined(SOKOL_NO_ENTRY)
4321int main(int argc, char* argv[]) {
4322 sapp_desc desc = sokol_main(argc, argv);
4323 _sapp_ios_run(&desc);
4324 return 0;
4325}
4326#endif /* SOKOL_NO_ENTRY */
4327
4328_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) {
4329 if (_sapp_events_enabled()) {
4330 _sapp_init_event(type);
4331 _sapp_call_event(&_sapp.event);
4332 }
4333}
4334
4335_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet<UITouch *>* touches, UIEvent* event) {
4336 if (_sapp_events_enabled()) {
4337 _sapp_init_event(type);
4338 NSEnumerator* enumerator = event.allTouches.objectEnumerator;
4339 UITouch* ios_touch;
4340 while ((ios_touch = [enumerator nextObject])) {
4341 if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) {
4342 CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view];
4343 sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++];
4344 cur_point->identifier = (uintptr_t) ios_touch;
4345 cur_point->pos_x = ios_pos.x * _sapp.dpi_scale;
4346 cur_point->pos_y = ios_pos.y * _sapp.dpi_scale;
4347 cur_point->changed = [touches containsObject:ios_touch];
4348 }
4349 }
4350 if (_sapp.event.num_touches > 0) {
4351 _sapp_call_event(&_sapp.event);
4352 }
4353 }
4354}
4355
4356_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) {
4357 CGRect screen_rect = UIScreen.mainScreen.bounds;
4358 _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale);
4359 _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale);
4360 _sapp.window_width = (int)roundf(screen_rect.size.width);
4361 _sapp.window_height = (int)roundf(screen_rect.size.height);
4362 int cur_fb_width, cur_fb_height;
4363 #if defined(SOKOL_METAL)
4364 const CGSize fb_size = _sapp.ios.view.drawableSize;
4365 cur_fb_width = (int)roundf(fb_size.width);
4366 cur_fb_height = (int)roundf(fb_size.height);
4367 #else
4368 cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth);
4369 cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight);
4370 #endif
4371 const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
4372 (_sapp.framebuffer_height != cur_fb_height);
4373 if (dim_changed) {
4374 #if defined(SOKOL_METAL)
4375 const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height };
4376 _sapp.ios.view.drawableSize = drawable_size;
4377 #else
4378 // nothing to do here, GLKView correctly respects the view's contentScaleFactor
4379 #endif
4380 if (!_sapp.first_frame) {
4381 _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED);
4382 }
4383 }
4384}
4385
4386_SOKOL_PRIVATE void _sapp_ios_frame(void) {
4387 _sapp_ios_update_dimensions();
4388 _sapp_frame();
4389}
4390
4391_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) {
4392 /* if not happened yet, create an invisible text field */
4393 if (nil == _sapp.ios.textfield) {
4394 _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init];
4395 _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)];
4396 _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault;
4397 _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault;
4398 _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone;
4399 _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo;
4400 _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo;
4401 _sapp.ios.textfield.hidden = YES;
4402 _sapp.ios.textfield.text = @"x";
4403 _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg;
4404 [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield];
4405
4406 [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg
4407 selector:@selector(keyboardWasShown:)
4408 name:UIKeyboardDidShowNotification object:nil];
4409 [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg
4410 selector:@selector(keyboardWillBeHidden:)
4411 name:UIKeyboardWillHideNotification object:nil];
4412 [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg
4413 selector:@selector(keyboardDidChangeFrame:)
4414 name:UIKeyboardDidChangeFrameNotification object:nil];
4415 }
4416 if (shown) {
4417 /* setting the text field as first responder brings up the onscreen keyboard */
4418 [_sapp.ios.textfield becomeFirstResponder];
4419 }
4420 else {
4421 [_sapp.ios.textfield resignFirstResponder];
4422 }
4423}
4424
4425@implementation _sapp_app_delegate
4426- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
4427 CGRect screen_rect = UIScreen.mainScreen.bounds;
4428 _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect];
4429 _sapp.window_width = (int)roundf(screen_rect.size.width);
4430 _sapp.window_height = (int)roundf(screen_rect.size.height);
4431 if (_sapp.desc.high_dpi) {
4432 _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale;
4433 }
4434 else {
4435 _sapp.dpi_scale = 1.0f;
4436 }
4437 _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale);
4438 _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale);
4439 NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond;
4440 #if defined(SOKOL_METAL)
4441 _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice();
4442 _sapp.ios.view = [[_sapp_ios_view alloc] init];
4443 _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval;
4444 _sapp.ios.view.device = _sapp.ios.mtl_device;
4445 _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
4446 _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
4447 _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count;
4448 /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor
4449 and automatically renders at Retina resolution. We'll disable
4450 autoResize and instead do the resizing in _sapp_ios_update_dimensions()
4451 */
4452 _sapp.ios.view.autoResizeDrawable = false;
4453 _sapp.ios.view.userInteractionEnabled = YES;
4454 _sapp.ios.view.multipleTouchEnabled = YES;
4455 _sapp.ios.view_ctrl = [[UIViewController alloc] init];
4456 _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen;
4457 _sapp.ios.view_ctrl.view = _sapp.ios.view;
4458 _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl;
4459 #else
4460 if (_sapp.desc.gl_force_gles2) {
4461 _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
4462 _sapp.gles2_fallback = true;
4463 }
4464 else {
4465 _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
4466 if (_sapp.ios.eagl_ctx == nil) {
4467 _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
4468 _sapp.gles2_fallback = true;
4469 }
4470 }
4471 _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect];
4472 _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
4473 _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
4474 _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
4475 GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone;
4476 _sapp.ios.view.drawableMultisample = msaa;
4477 _sapp.ios.view.context = _sapp.ios.eagl_ctx;
4478 _sapp.ios.view.enableSetNeedsDisplay = NO;
4479 _sapp.ios.view.userInteractionEnabled = YES;
4480 _sapp.ios.view.multipleTouchEnabled = YES;
4481 // on GLKView, contentScaleFactor appears to work just fine!
4482 if (_sapp.desc.high_dpi) {
4483 _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale;
4484 }
4485 else {
4486 _sapp.ios.view.contentScaleFactor = 1.0;
4487 }
4488 _sapp.ios.view_ctrl = [[GLKViewController alloc] init];
4489 _sapp.ios.view_ctrl.view = _sapp.ios.view;
4490 _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval;
4491 _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl;
4492 #endif
4493 [_sapp.ios.window makeKeyAndVisible];
4494
4495 _sapp.valid = true;
4496 return YES;
4497}
4498
4499- (void)applicationWillResignActive:(UIApplication *)application {
4500 if (!_sapp.ios.suspended) {
4501 _sapp.ios.suspended = true;
4502 _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED);
4503 }
4504}
4505
4506- (void)applicationDidBecomeActive:(UIApplication *)application {
4507 if (_sapp.ios.suspended) {
4508 _sapp.ios.suspended = false;
4509 _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED);
4510 }
4511}
4512
4513/* NOTE: this method will rarely ever be called, iOS application
4514 which are terminated by the user are usually killed via signal 9
4515 by the operating system.
4516*/
4517- (void)applicationWillTerminate:(UIApplication *)application {
4518 _SOKOL_UNUSED(application);
4519 _sapp_call_cleanup();
4520 _sapp_ios_discard_state();
4521 _sapp_discard_state();
4522}
4523@end
4524
4525@implementation _sapp_textfield_dlg
4526- (void)keyboardWasShown:(NSNotification*)notif {
4527 _sapp.onscreen_keyboard_shown = true;
4528 /* query the keyboard's size, and modify the content view's size */
4529 if (_sapp.desc.ios_keyboard_resizes_canvas) {
4530 NSDictionary* info = notif.userInfo;
4531 CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
4532 CGRect view_frame = UIScreen.mainScreen.bounds;
4533 view_frame.size.height -= kbd_h;
4534 _sapp.ios.view.frame = view_frame;
4535 }
4536}
4537- (void)keyboardWillBeHidden:(NSNotification*)notif {
4538 _sapp.onscreen_keyboard_shown = false;
4539 if (_sapp.desc.ios_keyboard_resizes_canvas) {
4540 _sapp.ios.view.frame = UIScreen.mainScreen.bounds;
4541 }
4542}
4543- (void)keyboardDidChangeFrame:(NSNotification*)notif {
4544 /* this is for the case when the screen rotation changes while the keyboard is open */
4545 if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) {
4546 NSDictionary* info = notif.userInfo;
4547 CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
4548 CGRect view_frame = UIScreen.mainScreen.bounds;
4549 view_frame.size.height -= kbd_h;
4550 _sapp.ios.view.frame = view_frame;
4551 }
4552}
4553- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string {
4554 if (_sapp_events_enabled()) {
4555 const NSUInteger len = string.length;
4556 if (len > 0) {
4557 for (NSUInteger i = 0; i < len; i++) {
4558 unichar c = [string characterAtIndex:i];
4559 if (c >= 32) {
4560 /* ignore surrogates for now */
4561 if ((c < 0xD800) || (c > 0xDFFF)) {
4562 _sapp_init_event(SAPP_EVENTTYPE_CHAR);
4563 _sapp.event.char_code = c;
4564 _sapp_call_event(&_sapp.event);
4565 }
4566 }
4567 if (c <= 32) {
4568 sapp_keycode k = SAPP_KEYCODE_INVALID;
4569 switch (c) {
4570 case 10: k = SAPP_KEYCODE_ENTER; break;
4571 case 32: k = SAPP_KEYCODE_SPACE; break;
4572 default: break;
4573 }
4574 if (k != SAPP_KEYCODE_INVALID) {
4575 _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN);
4576 _sapp.event.key_code = k;
4577 _sapp_call_event(&_sapp.event);
4578 _sapp_init_event(SAPP_EVENTTYPE_KEY_UP);
4579 _sapp.event.key_code = k;
4580 _sapp_call_event(&_sapp.event);
4581 }
4582 }
4583 }
4584 }
4585 else {
4586 /* this was a backspace */
4587 _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN);
4588 _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE;
4589 _sapp_call_event(&_sapp.event);
4590 _sapp_init_event(SAPP_EVENTTYPE_KEY_UP);
4591 _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE;
4592 _sapp_call_event(&_sapp.event);
4593 }
4594 }
4595 return NO;
4596}
4597@end
4598
4599@implementation _sapp_ios_view
4600- (void)drawRect:(CGRect)rect {
4601 _SOKOL_UNUSED(rect);
4602 _sapp_timing_measure(&_sapp.timing);
4603 @autoreleasepool {
4604 _sapp_ios_frame();
4605 }
4606}
4607- (BOOL)isOpaque {
4608 return YES;
4609}
4610- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event {
4611 _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event);
4612}
4613- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event {
4614 _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event);
4615}
4616- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event {
4617 _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event);
4618}
4619- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event {
4620 _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event);
4621}
4622@end
4623#endif /* TARGET_OS_IPHONE */
4624
4625#endif /* _SAPP_APPLE */
4626
4627/*== EMSCRIPTEN ==============================================================*/
4628#if defined(_SAPP_EMSCRIPTEN)
4629
4630#if defined(EM_JS_DEPS)
4631EM_JS_DEPS(sokol_app, "$withStackSave,$allocateUTF8OnStack");
4632#endif
4633
4634#ifdef __cplusplus
4635extern "C" {
4636#endif
4637
4638typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*);
4639
4640/* this function is called from a JS event handler when the user hides
4641 the onscreen keyboard pressing the 'dismiss keyboard key'
4642*/
4643EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) {
4644 _sapp.onscreen_keyboard_shown = false;
4645}
4646
4647EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) {
4648 if (_sapp.clipboard.enabled) {
4649 _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size);
4650 if (_sapp_events_enabled()) {
4651 _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
4652 _sapp_call_event(&_sapp.event);
4653 }
4654 }
4655}
4656
4657/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */
4658EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) {
4659 return _sapp.html5_ask_leave_site ? 1 : 0;
4660}
4661
4662EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) {
4663 if (!_sapp.drop.enabled) {
4664 return;
4665 }
4666 if (num < 0) {
4667 num = 0;
4668 }
4669 if (num > _sapp.drop.max_files) {
4670 num = _sapp.drop.max_files;
4671 }
4672 _sapp.drop.num_files = num;
4673 _sapp_clear_drop_buffer();
4674}
4675
4676EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) {
4677 /* NOTE: name is only the filename part, not a path */
4678 if (!_sapp.drop.enabled) {
4679 return;
4680 }
4681 if (0 == name) {
4682 return;
4683 }
4684 SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files);
4685 if ((i < 0) || (i >= _sapp.drop.num_files)) {
4686 return;
4687 }
4688 if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) {
4689 SAPP_LOG("sokol_app.h: dropped file path too long!\n");
4690 _sapp.drop.num_files = 0;
4691 }
4692}
4693
4694EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) {
4695 if (!_sapp.drop.enabled) {
4696 return;
4697 }
4698 if (0 == _sapp.drop.num_files) {
4699 /* there was an error copying the filenames */
4700 _sapp_clear_drop_buffer();
4701 return;
4702
4703 }
4704 if (_sapp_events_enabled()) {
4705 _sapp.mouse.x = (float)x * _sapp.dpi_scale;
4706 _sapp.mouse.y = (float)y * _sapp.dpi_scale;
4707 _sapp.mouse.dx = 0.0f;
4708 _sapp.mouse.dy = 0.0f;
4709 _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED);
4710 _sapp_call_event(&_sapp.event);
4711 }
4712}
4713
4714EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) {
4715 sapp_html5_fetch_response response;
4716 _sapp_clear(&response, sizeof(response));
4717 response.succeeded = (0 != success);
4718 response.error_code = (sapp_html5_fetch_error) error_code;
4719 response.file_index = index;
4720 response.data.ptr = buf_ptr;
4721 response.data.size = fetched_size;
4722 response.buffer.ptr = buf_ptr;
4723 response.buffer.size = buf_size;
4724 response.user_data = user_data;
4725 callback(&response);
4726}
4727
4728#ifdef __cplusplus
4729} /* extern "C" */
4730#endif
4731
4732/* Javascript helper functions for mobile virtual keyboard input */
4733EM_JS(void, sapp_js_create_textfield, (void), {
4734 const _sapp_inp = document.createElement("input");
4735 _sapp_inp.type = "text";
4736 _sapp_inp.id = "_sokol_app_input_element";
4737 _sapp_inp.autocapitalize = "none";
4738 _sapp_inp.addEventListener("focusout", function(_sapp_event) {
4739 __sapp_emsc_notify_keyboard_hidden()
4740
4741 });
4742 document.body.append(_sapp_inp);
4743});
4744
4745EM_JS(void, sapp_js_focus_textfield, (void), {
4746 document.getElementById("_sokol_app_input_element").focus();
4747});
4748
4749EM_JS(void, sapp_js_unfocus_textfield, (void), {
4750 document.getElementById("_sokol_app_input_element").blur();
4751});
4752
4753EM_JS(void, sapp_js_add_beforeunload_listener, (void), {
4754 Module.sokol_beforeunload = (event) => {
4755 if (__sapp_html5_get_ask_leave_site() != 0) {
4756 event.preventDefault();
4757 event.returnValue = ' ';
4758 }
4759 };
4760 window.addEventListener('beforeunload', Module.sokol_beforeunload);
4761});
4762
4763EM_JS(void, sapp_js_remove_beforeunload_listener, (void), {
4764 window.removeEventListener('beforeunload', Module.sokol_beforeunload);
4765});
4766
4767EM_JS(void, sapp_js_add_clipboard_listener, (void), {
4768 Module.sokol_paste = (event) => {
4769 const pasted_str = event.clipboardData.getData('text');
4770 withStackSave(() => {
4771 const cstr = allocateUTF8OnStack(pasted_str);
4772 __sapp_emsc_onpaste(cstr);
4773 });
4774 };
4775 window.addEventListener('paste', Module.sokol_paste);
4776});
4777
4778EM_JS(void, sapp_js_remove_clipboard_listener, (void), {
4779 window.removeEventListener('paste', Module.sokol_paste);
4780});
4781
4782EM_JS(void, sapp_js_write_clipboard, (const char* c_str), {
4783 const str = UTF8ToString(c_str);
4784 const ta = document.createElement('textarea');
4785 ta.setAttribute('autocomplete', 'off');
4786 ta.setAttribute('autocorrect', 'off');
4787 ta.setAttribute('autocapitalize', 'off');
4788 ta.setAttribute('spellcheck', 'false');
4789 ta.style.left = -100 + 'px';
4790 ta.style.top = -100 + 'px';
4791 ta.style.height = 1;
4792 ta.style.width = 1;
4793 ta.value = str;
4794 document.body.appendChild(ta);
4795 ta.select();
4796 document.execCommand('copy');
4797 document.body.removeChild(ta);
4798});
4799
4800_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) {
4801 sapp_js_write_clipboard(str);
4802}
4803
4804EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), {
4805 Module.sokol_drop_files = [];
4806 const canvas_name = UTF8ToString(canvas_name_cstr);
4807 const canvas = document.getElementById(canvas_name);
4808 Module.sokol_dragenter = (event) => {
4809 event.stopPropagation();
4810 event.preventDefault();
4811 };
4812 Module.sokol_dragleave = (event) => {
4813 event.stopPropagation();
4814 event.preventDefault();
4815 };
4816 Module.sokol_dragover = (event) => {
4817 event.stopPropagation();
4818 event.preventDefault();
4819 };
4820 Module.sokol_drop = (event) => {
4821 event.stopPropagation();
4822 event.preventDefault();
4823 const files = event.dataTransfer.files;
4824 Module.sokol_dropped_files = files;
4825 __sapp_emsc_begin_drop(files.length);
4826 for (let i = 0; i < files.length; i++) {
4827 withStackSave(() => {
4828 const cstr = allocateUTF8OnStack(files[i].name);
4829 __sapp_emsc_drop(i, cstr);
4830 });
4831 }
4832 // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect
4833 __sapp_emsc_end_drop(event.clientX, event.clientY);
4834 };
4835 canvas.addEventListener('dragenter', Module.sokol_dragenter, false);
4836 canvas.addEventListener('dragleave', Module.sokol_dragleave, false);
4837 canvas.addEventListener('dragover', Module.sokol_dragover, false);
4838 canvas.addEventListener('drop', Module.sokol_drop, false);
4839});
4840
4841EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), {
4842 \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
4843 const files = Module.sokol_dropped_files;
4844 if ((index < 0) || (index >= files.length)) {
4845 return 0;
4846 }
4847 else {
4848 return files[index].size;
4849 }
4850});
4851
4852EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), {
4853 const reader = new FileReader();
4854 reader.onload = (loadEvent) => {
4855 const content = loadEvent.target.result;
4856 if (content.byteLength > buf_size) {
4857 // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL
4858 __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data);
4859 }
4860 else {
4861 HEAPU8.set(new Uint8Array(content), buf_ptr);
4862 __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data);
4863 }
4864 };
4865 reader.onerror = () => {
4866 // SAPP_HTML5_FETCH_ERROR_OTHER
4867 __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data);
4868 };
4869 \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
4870 const files = Module.sokol_dropped_files;
4871 reader.readAsArrayBuffer(files[index]);
4872});
4873
4874EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), {
4875 const canvas_name = UTF8ToString(canvas_name_cstr);
4876 const canvas = document.getElementById(canvas_name);
4877 canvas.removeEventListener('dragenter', Module.sokol_dragenter);
4878 canvas.removeEventListener('dragleave', Module.sokol_dragleave);
4879 canvas.removeEventListener('dragover', Module.sokol_dragover);
4880 canvas.removeEventListener('drop', Module.sokol_drop);
4881});
4882
4883/* called from the emscripten event handler to update the keyboard visibility
4884 state, this must happen from an JS input event handler, otherwise
4885 the request will be ignored by the browser
4886*/
4887_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) {
4888 if (_sapp.emsc.wants_show_keyboard) {
4889 /* create input text field on demand */
4890 if (!_sapp.emsc.textfield_created) {
4891 _sapp.emsc.textfield_created = true;
4892 sapp_js_create_textfield();
4893 }
4894 /* focus the text input field, this will bring up the keyboard */
4895 _sapp.onscreen_keyboard_shown = true;
4896 _sapp.emsc.wants_show_keyboard = false;
4897 sapp_js_focus_textfield();
4898 }
4899 if (_sapp.emsc.wants_hide_keyboard) {
4900 /* unfocus the text input field */
4901 if (_sapp.emsc.textfield_created) {
4902 _sapp.onscreen_keyboard_shown = false;
4903 _sapp.emsc.wants_hide_keyboard = false;
4904 sapp_js_unfocus_textfield();
4905 }
4906 }
4907}
4908
4909/* actually showing the onscreen keyboard must be initiated from a JS
4910 input event handler, so we'll just keep track of the desired
4911 state, and the actual state change will happen with the next input event
4912*/
4913_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) {
4914 if (show) {
4915 _sapp.emsc.wants_show_keyboard = true;
4916 }
4917 else {
4918 _sapp.emsc.wants_hide_keyboard = true;
4919 }
4920}
4921
4922EM_JS(void, sapp_js_init, (const char* c_str_target), {
4923 // lookup and store canvas object by name
4924 const target_str = UTF8ToString(c_str_target);
4925 Module.sapp_emsc_target = document.getElementById(target_str);
4926 if (!Module.sapp_emsc_target) {
4927 console.log("sokol_app.h: invalid target:" + target_str);
4928 }
4929 if (!Module.sapp_emsc_target.requestPointerLock) {
4930 console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str);
4931 }
4932});
4933
4934_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) {
4935 _SOKOL_UNUSED(emsc_type);
4936 _SOKOL_UNUSED(user_data);
4937 _sapp.mouse.locked = emsc_event->isActive;
4938 return EM_TRUE;
4939}
4940
4941_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) {
4942 _SOKOL_UNUSED(emsc_type);
4943 _SOKOL_UNUSED(reserved);
4944 _SOKOL_UNUSED(user_data);
4945 _sapp.mouse.locked = false;
4946 _sapp.emsc.mouse_lock_requested = false;
4947 return true;
4948}
4949
4950EM_JS(void, sapp_js_request_pointerlock, (void), {
4951 if (Module.sapp_emsc_target) {
4952 if (Module.sapp_emsc_target.requestPointerLock) {
4953 Module.sapp_emsc_target.requestPointerLock();
4954 }
4955 }
4956});
4957
4958EM_JS(void, sapp_js_exit_pointerlock, (void), {
4959 if (document.exitPointerLock) {
4960 document.exitPointerLock();
4961 }
4962});
4963
4964_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) {
4965 if (lock) {
4966 /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */
4967 _sapp.emsc.mouse_lock_requested = true;
4968 }
4969 else {
4970 /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */
4971 _sapp.emsc.mouse_lock_requested = false;
4972 sapp_js_exit_pointerlock();
4973 }
4974}
4975
4976/* called from inside event handlers to check if mouse lock had been requested,
4977 and if yes, actually enter mouse lock.
4978*/
4979_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) {
4980 if (_sapp.emsc.mouse_lock_requested) {
4981 _sapp.emsc.mouse_lock_requested = false;
4982 sapp_js_request_pointerlock();
4983 }
4984}
4985
4986// set mouse cursor type
4987EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), {
4988 if (Module.sapp_emsc_target) {
4989 let cursor;
4990 if (shown === 0) {
4991 cursor = "none";
4992 }
4993 else switch (cursor_type) {
4994 case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT
4995 case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW
4996 case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM
4997 case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR
4998 case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND
4999 case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW
5000 case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS
5001 case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE
5002 case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW
5003 case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL
5004 case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED
5005 default: cursor = "auto"; break;
5006 }
5007 Module.sapp_emsc_target.style.cursor = cursor;
5008 }
5009});
5010
5011_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) {
5012 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
5013 sapp_js_set_cursor((int)cursor, shown ? 1 : 0);
5014}
5015
5016/* JS helper functions to update browser tab favicon */
5017EM_JS(void, sapp_js_clear_favicon, (void), {
5018 const link = document.getElementById('sokol-app-favicon');
5019 if (link) {
5020 document.head.removeChild(link);
5021 }
5022});
5023
5024EM_JS(void, sapp_js_set_favicon, (int w, int h, const uint8_t* pixels), {
5025 const canvas = document.createElement('canvas');
5026 canvas.width = w;
5027 canvas.height = h;
5028 const ctx = canvas.getContext('2d');
5029 const img_data = ctx.createImageData(w, h);
5030 img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4));
5031 ctx.putImageData(img_data, 0, 0);
5032 const new_link = document.createElement('link');
5033 new_link.id = 'sokol-app-favicon';
5034 new_link.rel = 'shortcut icon';
5035 new_link.href = canvas.toDataURL();
5036 document.head.appendChild(new_link);
5037});
5038
5039_SOKOL_PRIVATE void _sapp_emsc_set_icon(const sapp_icon_desc* icon_desc, int num_images) {
5040 SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES));
5041 sapp_js_clear_favicon();
5042 // find the best matching image candidate for 16x16 pixels
5043 int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, 16, 16);
5044 const sapp_image_desc* img_desc = &icon_desc->images[img_index];
5045 sapp_js_set_favicon(img_desc->width, img_desc->height, (const uint8_t*) img_desc->pixels.ptr);
5046}
5047
5048#if defined(SOKOL_WGPU)
5049_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void);
5050_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void);
5051#endif
5052
5053_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_button_mods(uint16_t buttons) {
5054 uint32_t m = 0;
5055 if (0 != (buttons & (1<<0))) { m |= SAPP_MODIFIER_LMB; }
5056 if (0 != (buttons & (1<<1))) { m |= SAPP_MODIFIER_RMB; } // not a bug
5057 if (0 != (buttons & (1<<2))) { m |= SAPP_MODIFIER_MMB; } // not a bug
5058 return m;
5059}
5060
5061_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_event_mods(const EmscriptenMouseEvent* ev) {
5062 uint32_t m = 0;
5063 if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; }
5064 if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; }
5065 if (ev->altKey) { m |= SAPP_MODIFIER_ALT; }
5066 if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; }
5067 m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons);
5068 return m;
5069}
5070
5071_SOKOL_PRIVATE uint32_t _sapp_emsc_key_event_mods(const EmscriptenKeyboardEvent* ev) {
5072 uint32_t m = 0;
5073 if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; }
5074 if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; }
5075 if (ev->altKey) { m |= SAPP_MODIFIER_ALT; }
5076 if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; }
5077 m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons);
5078 return m;
5079}
5080
5081_SOKOL_PRIVATE uint32_t _sapp_emsc_touch_event_mods(const EmscriptenTouchEvent* ev) {
5082 uint32_t m = 0;
5083 if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; }
5084 if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; }
5085 if (ev->altKey) { m |= SAPP_MODIFIER_ALT; }
5086 if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; }
5087 m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons);
5088 return m;
5089}
5090
5091_SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) {
5092 _SOKOL_UNUSED(event_type);
5093 _SOKOL_UNUSED(user_data);
5094 double w, h;
5095 emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h);
5096 /* The above method might report zero when toggling HTML5 fullscreen,
5097 in that case use the window's inner width reported by the
5098 emscripten event. This works ok when toggling *into* fullscreen
5099 but doesn't properly restore the previous canvas size when switching
5100 back from fullscreen.
5101
5102 In general, due to the HTML5's fullscreen API's flaky nature it is
5103 recommended to use 'soft fullscreen' (stretching the WebGL canvas
5104 over the browser windows client rect) with a CSS definition like this:
5105
5106 position: absolute;
5107 top: 0px;
5108 left: 0px;
5109 margin: 0px;
5110 border: 0;
5111 width: 100%;
5112 height: 100%;
5113 overflow: hidden;
5114 display: block;
5115 */
5116 if (w < 1.0) {
5117 w = ui_event->windowInnerWidth;
5118 }
5119 else {
5120 _sapp.window_width = (int)roundf(w);
5121 }
5122 if (h < 1.0) {
5123 h = ui_event->windowInnerHeight;
5124 }
5125 else {
5126 _sapp.window_height = (int)roundf(h);
5127 }
5128 if (_sapp.desc.high_dpi) {
5129 _sapp.dpi_scale = emscripten_get_device_pixel_ratio();
5130 }
5131 _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale);
5132 _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale);
5133 SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0));
5134 emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height);
5135 #if defined(SOKOL_WGPU)
5136 /* on WebGPU: recreate size-dependent rendering surfaces */
5137 _sapp_emsc_wgpu_surfaces_discard();
5138 _sapp_emsc_wgpu_surfaces_create();
5139 #endif
5140 if (_sapp_events_enabled()) {
5141 _sapp_init_event(SAPP_EVENTTYPE_RESIZED);
5142 _sapp_call_event(&_sapp.event);
5143 }
5144 return true;
5145}
5146
5147_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) {
5148 _SOKOL_UNUSED(user_data);
5149 _sapp.emsc.mouse_buttons = emsc_event->buttons;
5150 if (_sapp.mouse.locked) {
5151 _sapp.mouse.dx = (float) emsc_event->movementX;
5152 _sapp.mouse.dy = (float) emsc_event->movementY;
5153 }
5154 else {
5155 float new_x = emsc_event->targetX * _sapp.dpi_scale;
5156 float new_y = emsc_event->targetY * _sapp.dpi_scale;
5157 if (_sapp.mouse.pos_valid) {
5158 _sapp.mouse.dx = new_x - _sapp.mouse.x;
5159 _sapp.mouse.dy = new_y - _sapp.mouse.y;
5160 }
5161 _sapp.mouse.x = new_x;
5162 _sapp.mouse.y = new_y;
5163 _sapp.mouse.pos_valid = true;
5164 }
5165 if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) {
5166 sapp_event_type type;
5167 bool is_button_event = false;
5168 switch (emsc_type) {
5169 case EMSCRIPTEN_EVENT_MOUSEDOWN:
5170 type = SAPP_EVENTTYPE_MOUSE_DOWN;
5171 is_button_event = true;
5172 break;
5173 case EMSCRIPTEN_EVENT_MOUSEUP:
5174 type = SAPP_EVENTTYPE_MOUSE_UP;
5175 is_button_event = true;
5176 break;
5177 case EMSCRIPTEN_EVENT_MOUSEMOVE:
5178 type = SAPP_EVENTTYPE_MOUSE_MOVE;
5179 break;
5180 case EMSCRIPTEN_EVENT_MOUSEENTER:
5181 type = SAPP_EVENTTYPE_MOUSE_ENTER;
5182 break;
5183 case EMSCRIPTEN_EVENT_MOUSELEAVE:
5184 type = SAPP_EVENTTYPE_MOUSE_LEAVE;
5185 break;
5186 default:
5187 type = SAPP_EVENTTYPE_INVALID;
5188 break;
5189 }
5190 if (type != SAPP_EVENTTYPE_INVALID) {
5191 _sapp_init_event(type);
5192 _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(emsc_event);
5193 if (is_button_event) {
5194 switch (emsc_event->button) {
5195 case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break;
5196 case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break;
5197 case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break;
5198 default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break;
5199 }
5200 }
5201 else {
5202 _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID;
5203 }
5204 _sapp_call_event(&_sapp.event);
5205 }
5206 /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */
5207 if (is_button_event) {
5208 _sapp_emsc_update_mouse_lock_state();
5209 }
5210 }
5211 _sapp_emsc_update_keyboard_state();
5212 return true;
5213}
5214
5215_SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) {
5216 _SOKOL_UNUSED(emsc_type);
5217 _SOKOL_UNUSED(user_data);
5218 _sapp.emsc.mouse_buttons = emsc_event->mouse.buttons;
5219 if (_sapp_events_enabled()) {
5220 _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL);
5221 _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(&emsc_event->mouse);
5222 /* see https://github.com/floooh/sokol/issues/339 */
5223 float scale;
5224 switch (emsc_event->deltaMode) {
5225 case DOM_DELTA_PIXEL: scale = -0.04f; break;
5226 case DOM_DELTA_LINE: scale = -1.33f; break;
5227 case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess
5228 default: scale = -0.1f; break; // shouldn't happen
5229 }
5230 _sapp.event.scroll_x = scale * (float)emsc_event->deltaX;
5231 _sapp.event.scroll_y = scale * (float)emsc_event->deltaY;
5232 _sapp_call_event(&_sapp.event);
5233 }
5234 _sapp_emsc_update_keyboard_state();
5235 _sapp_emsc_update_mouse_lock_state();
5236 return true;
5237}
5238
5239static struct {
5240 const char* str;
5241 sapp_keycode code;
5242} _sapp_emsc_keymap[] = {
5243 { "Backspace", SAPP_KEYCODE_BACKSPACE },
5244 { "Tab", SAPP_KEYCODE_TAB },
5245 { "Enter", SAPP_KEYCODE_ENTER },
5246 { "ShiftLeft", SAPP_KEYCODE_LEFT_SHIFT },
5247 { "ShiftRight", SAPP_KEYCODE_RIGHT_SHIFT },
5248 { "ControlLeft", SAPP_KEYCODE_LEFT_CONTROL },
5249 { "ControlRight", SAPP_KEYCODE_RIGHT_CONTROL },
5250 { "AltLeft", SAPP_KEYCODE_LEFT_ALT },
5251 { "AltRight", SAPP_KEYCODE_RIGHT_ALT },
5252 { "Pause", SAPP_KEYCODE_PAUSE },
5253 { "CapsLock", SAPP_KEYCODE_CAPS_LOCK },
5254 { "Escape", SAPP_KEYCODE_ESCAPE },
5255 { "Space", SAPP_KEYCODE_SPACE },
5256 { "PageUp", SAPP_KEYCODE_PAGE_UP },
5257 { "PageDown", SAPP_KEYCODE_PAGE_DOWN },
5258 { "End", SAPP_KEYCODE_END },
5259 { "Home", SAPP_KEYCODE_HOME },
5260 { "ArrowLeft", SAPP_KEYCODE_LEFT },
5261 { "ArrowUp", SAPP_KEYCODE_UP },
5262 { "ArrowRight", SAPP_KEYCODE_RIGHT },
5263 { "ArrowDown", SAPP_KEYCODE_DOWN },
5264 { "PrintScreen", SAPP_KEYCODE_PRINT_SCREEN },
5265 { "Insert", SAPP_KEYCODE_INSERT },
5266 { "Delete", SAPP_KEYCODE_DELETE },
5267 { "Digit0", SAPP_KEYCODE_0 },
5268 { "Digit1", SAPP_KEYCODE_1 },
5269 { "Digit2", SAPP_KEYCODE_2 },
5270 { "Digit3", SAPP_KEYCODE_3 },
5271 { "Digit4", SAPP_KEYCODE_4 },
5272 { "Digit5", SAPP_KEYCODE_5 },
5273 { "Digit6", SAPP_KEYCODE_6 },
5274 { "Digit7", SAPP_KEYCODE_7 },
5275 { "Digit8", SAPP_KEYCODE_8 },
5276 { "Digit9", SAPP_KEYCODE_9 },
5277 { "KeyA", SAPP_KEYCODE_A },
5278 { "KeyB", SAPP_KEYCODE_B },
5279 { "KeyC", SAPP_KEYCODE_C },
5280 { "KeyD", SAPP_KEYCODE_D },
5281 { "KeyE", SAPP_KEYCODE_E },
5282 { "KeyF", SAPP_KEYCODE_F },
5283 { "KeyG", SAPP_KEYCODE_G },
5284 { "KeyH", SAPP_KEYCODE_H },
5285 { "KeyI", SAPP_KEYCODE_I },
5286 { "KeyJ", SAPP_KEYCODE_J },
5287 { "KeyK", SAPP_KEYCODE_K },
5288 { "KeyL", SAPP_KEYCODE_L },
5289 { "KeyM", SAPP_KEYCODE_M },
5290 { "KeyN", SAPP_KEYCODE_N },
5291 { "KeyO", SAPP_KEYCODE_O },
5292 { "KeyP", SAPP_KEYCODE_P },
5293 { "KeyQ", SAPP_KEYCODE_Q },
5294 { "KeyR", SAPP_KEYCODE_R },
5295 { "KeyS", SAPP_KEYCODE_S },
5296 { "KeyT", SAPP_KEYCODE_T },
5297 { "KeyU", SAPP_KEYCODE_U },
5298 { "KeyV", SAPP_KEYCODE_V },
5299 { "KeyW", SAPP_KEYCODE_W },
5300 { "KeyX", SAPP_KEYCODE_X },
5301 { "KeyY", SAPP_KEYCODE_Y },
5302 { "KeyZ", SAPP_KEYCODE_Z },
5303 { "MetaLeft", SAPP_KEYCODE_LEFT_SUPER },
5304 { "MetaRight", SAPP_KEYCODE_RIGHT_SUPER },
5305 { "Numpad0", SAPP_KEYCODE_KP_0 },
5306 { "Numpad1", SAPP_KEYCODE_KP_1 },
5307 { "Numpad2", SAPP_KEYCODE_KP_2 },
5308 { "Numpad3", SAPP_KEYCODE_KP_3 },
5309 { "Numpad4", SAPP_KEYCODE_KP_4 },
5310 { "Numpad5", SAPP_KEYCODE_KP_5 },
5311 { "Numpad6", SAPP_KEYCODE_KP_6 },
5312 { "Numpad7", SAPP_KEYCODE_KP_7 },
5313 { "Numpad8", SAPP_KEYCODE_KP_8 },
5314 { "Numpad9", SAPP_KEYCODE_KP_9 },
5315 { "NumpadMultiply", SAPP_KEYCODE_KP_MULTIPLY },
5316 { "NumpadAdd", SAPP_KEYCODE_KP_ADD },
5317 { "NumpadSubtract", SAPP_KEYCODE_KP_SUBTRACT },
5318 { "NumpadDecimal", SAPP_KEYCODE_KP_DECIMAL },
5319 { "NumpadDivide", SAPP_KEYCODE_KP_DIVIDE },
5320 { "F1", SAPP_KEYCODE_F1 },
5321 { "F2", SAPP_KEYCODE_F2 },
5322 { "F3", SAPP_KEYCODE_F3 },
5323 { "F4", SAPP_KEYCODE_F4 },
5324 { "F5", SAPP_KEYCODE_F5 },
5325 { "F6", SAPP_KEYCODE_F6 },
5326 { "F7", SAPP_KEYCODE_F7 },
5327 { "F8", SAPP_KEYCODE_F8 },
5328 { "F9", SAPP_KEYCODE_F9 },
5329 { "F10", SAPP_KEYCODE_F10 },
5330 { "F11", SAPP_KEYCODE_F11 },
5331 { "F12", SAPP_KEYCODE_F12 },
5332 { "NumLock", SAPP_KEYCODE_NUM_LOCK },
5333 { "ScrollLock", SAPP_KEYCODE_SCROLL_LOCK },
5334 { "Semicolon", SAPP_KEYCODE_SEMICOLON },
5335 { "Equal", SAPP_KEYCODE_EQUAL },
5336 { "Comma", SAPP_KEYCODE_COMMA },
5337 { "Minus", SAPP_KEYCODE_MINUS },
5338 { "Period", SAPP_KEYCODE_PERIOD },
5339 { "Slash", SAPP_KEYCODE_SLASH },
5340 { "Backquote", SAPP_KEYCODE_GRAVE_ACCENT },
5341 { "BracketLeft", SAPP_KEYCODE_LEFT_BRACKET },
5342 { "Backslash", SAPP_KEYCODE_BACKSLASH },
5343 { "BracketRight", SAPP_KEYCODE_RIGHT_BRACKET },
5344 { "Quote", SAPP_KEYCODE_GRAVE_ACCENT }, // FIXME: ???
5345 { 0, SAPP_KEYCODE_INVALID },
5346};
5347
5348_SOKOL_PRIVATE sapp_keycode _sapp_emsc_translate_key(const char* str) {
5349 int i = 0;
5350 const char* keystr;
5351 while (( keystr = _sapp_emsc_keymap[i].str )) {
5352 if (0 == strcmp(str, keystr)) {
5353 return _sapp_emsc_keymap[i].code;
5354 }
5355 i += 1;
5356 }
5357 return SAPP_KEYCODE_INVALID;
5358}
5359
5360_SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) {
5361 _SOKOL_UNUSED(user_data);
5362 bool retval = true;
5363 if (_sapp_events_enabled()) {
5364 sapp_event_type type;
5365 switch (emsc_type) {
5366 case EMSCRIPTEN_EVENT_KEYDOWN:
5367 type = SAPP_EVENTTYPE_KEY_DOWN;
5368 break;
5369 case EMSCRIPTEN_EVENT_KEYUP:
5370 type = SAPP_EVENTTYPE_KEY_UP;
5371 break;
5372 case EMSCRIPTEN_EVENT_KEYPRESS:
5373 type = SAPP_EVENTTYPE_CHAR;
5374 break;
5375 default:
5376 type = SAPP_EVENTTYPE_INVALID;
5377 break;
5378 }
5379 if (type != SAPP_EVENTTYPE_INVALID) {
5380 bool send_keyup_followup = false;
5381 _sapp_init_event(type);
5382 _sapp.event.key_repeat = emsc_event->repeat;
5383 _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event);
5384 if (type == SAPP_EVENTTYPE_CHAR) {
5385 _sapp.event.char_code = emsc_event->charCode;
5386 /* workaround to make Cmd+V work on Safari */
5387 if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) {
5388 retval = false;
5389 }
5390 }
5391 else {
5392 _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code);
5393 /* Special hack for macOS: if the Super key is pressed, macOS doesn't
5394 send keyUp events. As a workaround, to prevent keys from
5395 "sticking", we'll send a keyup event following a keydown
5396 when the SUPER key is pressed
5397 */
5398 if ((type == SAPP_EVENTTYPE_KEY_DOWN) &&
5399 (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) &&
5400 (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) &&
5401 (_sapp.event.modifiers & SAPP_MODIFIER_SUPER))
5402 {
5403 send_keyup_followup = true;
5404 }
5405 /* only forward a certain key ranges to the browser */
5406 switch (_sapp.event.key_code) {
5407 case SAPP_KEYCODE_WORLD_1:
5408 case SAPP_KEYCODE_WORLD_2:
5409 case SAPP_KEYCODE_ESCAPE:
5410 case SAPP_KEYCODE_ENTER:
5411 case SAPP_KEYCODE_TAB:
5412 case SAPP_KEYCODE_BACKSPACE:
5413 case SAPP_KEYCODE_INSERT:
5414 case SAPP_KEYCODE_DELETE:
5415 case SAPP_KEYCODE_RIGHT:
5416 case SAPP_KEYCODE_LEFT:
5417 case SAPP_KEYCODE_DOWN:
5418 case SAPP_KEYCODE_UP:
5419 case SAPP_KEYCODE_PAGE_UP:
5420 case SAPP_KEYCODE_PAGE_DOWN:
5421 case SAPP_KEYCODE_HOME:
5422 case SAPP_KEYCODE_END:
5423 case SAPP_KEYCODE_CAPS_LOCK:
5424 case SAPP_KEYCODE_SCROLL_LOCK:
5425 case SAPP_KEYCODE_NUM_LOCK:
5426 case SAPP_KEYCODE_PRINT_SCREEN:
5427 case SAPP_KEYCODE_PAUSE:
5428 case SAPP_KEYCODE_F1:
5429 case SAPP_KEYCODE_F2:
5430 case SAPP_KEYCODE_F3:
5431 case SAPP_KEYCODE_F4:
5432 case SAPP_KEYCODE_F5:
5433 case SAPP_KEYCODE_F6:
5434 case SAPP_KEYCODE_F7:
5435 case SAPP_KEYCODE_F8:
5436 case SAPP_KEYCODE_F9:
5437 case SAPP_KEYCODE_F10:
5438 case SAPP_KEYCODE_F11:
5439 case SAPP_KEYCODE_F12:
5440 case SAPP_KEYCODE_F13:
5441 case SAPP_KEYCODE_F14:
5442 case SAPP_KEYCODE_F15:
5443 case SAPP_KEYCODE_F16:
5444 case SAPP_KEYCODE_F17:
5445 case SAPP_KEYCODE_F18:
5446 case SAPP_KEYCODE_F19:
5447 case SAPP_KEYCODE_F20:
5448 case SAPP_KEYCODE_F21:
5449 case SAPP_KEYCODE_F22:
5450 case SAPP_KEYCODE_F23:
5451 case SAPP_KEYCODE_F24:
5452 case SAPP_KEYCODE_F25:
5453 case SAPP_KEYCODE_LEFT_SHIFT:
5454 case SAPP_KEYCODE_LEFT_CONTROL:
5455 case SAPP_KEYCODE_LEFT_ALT:
5456 case SAPP_KEYCODE_LEFT_SUPER:
5457 case SAPP_KEYCODE_RIGHT_SHIFT:
5458 case SAPP_KEYCODE_RIGHT_CONTROL:
5459 case SAPP_KEYCODE_RIGHT_ALT:
5460 case SAPP_KEYCODE_RIGHT_SUPER:
5461 case SAPP_KEYCODE_MENU:
5462 /* consume the event */
5463 break;
5464 default:
5465 /* forward key to browser */
5466 retval = false;
5467 break;
5468 }
5469 }
5470 if (_sapp_call_event(&_sapp.event)) {
5471 /* consume event via sapp_consume_event() */
5472 retval = true;
5473 }
5474 if (send_keyup_followup) {
5475 _sapp.event.type = SAPP_EVENTTYPE_KEY_UP;
5476 if (_sapp_call_event(&_sapp.event)) {
5477 retval = true;
5478 }
5479 }
5480 }
5481 }
5482 _sapp_emsc_update_keyboard_state();
5483 _sapp_emsc_update_mouse_lock_state();
5484 return retval;
5485}
5486
5487_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) {
5488 _SOKOL_UNUSED(user_data);
5489 bool retval = true;
5490 if (_sapp_events_enabled()) {
5491 sapp_event_type type;
5492 switch (emsc_type) {
5493 case EMSCRIPTEN_EVENT_TOUCHSTART:
5494 type = SAPP_EVENTTYPE_TOUCHES_BEGAN;
5495 break;
5496 case EMSCRIPTEN_EVENT_TOUCHMOVE:
5497 type = SAPP_EVENTTYPE_TOUCHES_MOVED;
5498 break;
5499 case EMSCRIPTEN_EVENT_TOUCHEND:
5500 type = SAPP_EVENTTYPE_TOUCHES_ENDED;
5501 break;
5502 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
5503 type = SAPP_EVENTTYPE_TOUCHES_CANCELLED;
5504 break;
5505 default:
5506 type = SAPP_EVENTTYPE_INVALID;
5507 retval = false;
5508 break;
5509 }
5510 if (type != SAPP_EVENTTYPE_INVALID) {
5511 _sapp_init_event(type);
5512 _sapp.event.modifiers = _sapp_emsc_touch_event_mods(emsc_event);
5513 _sapp.event.num_touches = emsc_event->numTouches;
5514 if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) {
5515 _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS;
5516 }
5517 for (int i = 0; i < _sapp.event.num_touches; i++) {
5518 const EmscriptenTouchPoint* src = &emsc_event->touches[i];
5519 sapp_touchpoint* dst = &_sapp.event.touches[i];
5520 dst->identifier = (uintptr_t)src->identifier;
5521 dst->pos_x = src->targetX * _sapp.dpi_scale;
5522 dst->pos_y = src->targetY * _sapp.dpi_scale;
5523 dst->changed = src->isChanged;
5524 }
5525 _sapp_call_event(&_sapp.event);
5526 }
5527 }
5528 _sapp_emsc_update_keyboard_state();
5529 return retval;
5530}
5531
5532_SOKOL_PRIVATE EM_BOOL _sapp_emsc_focus_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) {
5533 _SOKOL_UNUSED(emsc_type);
5534 _SOKOL_UNUSED(emsc_event);
5535 _SOKOL_UNUSED(user_data);
5536 if (_sapp_events_enabled()) {
5537 _sapp_init_event(SAPP_EVENTTYPE_FOCUSED);
5538 _sapp_call_event(&_sapp.event);
5539 }
5540 return true;
5541}
5542
5543_SOKOL_PRIVATE EM_BOOL _sapp_emsc_blur_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) {
5544 _SOKOL_UNUSED(emsc_type);
5545 _SOKOL_UNUSED(emsc_event);
5546 _SOKOL_UNUSED(user_data);
5547 if (_sapp_events_enabled()) {
5548 _sapp_init_event(SAPP_EVENTTYPE_UNFOCUSED);
5549 _sapp_call_event(&_sapp.event);
5550 }
5551 return true;
5552}
5553
5554#if defined(SOKOL_GLES2) || defined(SOKOL_GLES3)
5555_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) {
5556 _SOKOL_UNUSED(reserved);
5557 _SOKOL_UNUSED(user_data);
5558 sapp_event_type type;
5559 switch (emsc_type) {
5560 case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break;
5561 case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break;
5562 default: type = SAPP_EVENTTYPE_INVALID; break;
5563 }
5564 if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) {
5565 _sapp_init_event(type);
5566 _sapp_call_event(&_sapp.event);
5567 }
5568 return true;
5569}
5570
5571_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) {
5572 EmscriptenWebGLContextAttributes attrs;
5573 emscripten_webgl_init_context_attributes(&attrs);
5574 attrs.alpha = _sapp.desc.alpha;
5575 attrs.depth = true;
5576 attrs.stencil = true;
5577 attrs.antialias = _sapp.sample_count > 1;
5578 attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha;
5579 attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer;
5580 attrs.enableExtensionsByDefault = true;
5581 #if defined(SOKOL_GLES3)
5582 if (_sapp.desc.gl_force_gles2) {
5583 attrs.majorVersion = 1;
5584 _sapp.gles2_fallback = true;
5585 }
5586 else {
5587 attrs.majorVersion = 2;
5588 }
5589 #endif
5590 EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs);
5591 if (!ctx) {
5592 attrs.majorVersion = 1;
5593 ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs);
5594 _sapp.gles2_fallback = true;
5595 }
5596 emscripten_webgl_make_context_current(ctx);
5597
5598 /* some WebGL extension are not enabled automatically by emscripten */
5599 emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc");
5600}
5601#endif
5602
5603#if defined(SOKOL_WGPU)
5604#define _SAPP_EMSC_WGPU_STATE_INITIAL (0)
5605#define _SAPP_EMSC_WGPU_STATE_READY (1)
5606#define _SAPP_EMSC_WGPU_STATE_RUNNING (2)
5607
5608#if defined(__cplusplus)
5609extern "C" {
5610#endif
5611/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */
5612EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) {
5613 SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device);
5614 _sapp.emsc.wgpu.device = (WGPUDevice) device_id;
5615 _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id;
5616 _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt;
5617 _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY;
5618}
5619#if defined(__cplusplus)
5620} // extern "C"
5621#endif
5622
5623/* embedded JS function to handle all the asynchronous WebGPU setup */
5624EM_JS(void, sapp_js_wgpu_init, (), {
5625 WebGPU.initManagers();
5626 // FIXME: the extension activation must be more clever here
5627 navigator.gpu.requestAdapter().then((adapter) => {
5628 console.log("wgpu adapter extensions: " + adapter.extensions);
5629 adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then((device) => {
5630 var gpuContext = document.getElementById("canvas").getContext("gpupresent");
5631 console.log("wgpu device extensions: " + adapter.extensions);
5632 gpuContext.getSwapChainPreferredFormat(device).then((fmt) => {
5633 const swapChainDescriptor = { device: device, format: fmt };
5634 const swapChain = gpuContext.configureSwapChain(swapChainDescriptor);
5635 const deviceId = WebGPU.mgrDevice.create(device);
5636 const swapChainId = WebGPU.mgrSwapChain.create(swapChain);
5637 const fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; });
5638 console.log("wgpu device: " + device);
5639 console.log("wgpu swap chain: " + swapChain);
5640 console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")");
5641 __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId);
5642 });
5643 });
5644 });
5645});
5646
5647_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) {
5648 SOKOL_ASSERT(_sapp.emsc.wgpu.device);
5649 SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain);
5650 SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex);
5651 SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view);
5652 SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex);
5653 SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view);
5654
5655 WGPUTextureDescriptor ds_desc;
5656 _sapp_clear(&ds_desc, sizeof(ds_desc));
5657 ds_desc.usage = WGPUTextureUsage_OutputAttachment;
5658 ds_desc.dimension = WGPUTextureDimension_2D;
5659 ds_desc.size.width = (uint32_t) _sapp.framebuffer_width;
5660 ds_desc.size.height = (uint32_t) _sapp.framebuffer_height;
5661 ds_desc.size.depth = 1;
5662 ds_desc.arrayLayerCount = 1;
5663 ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8;
5664 ds_desc.mipLevelCount = 1;
5665 ds_desc.sampleCount = _sapp.sample_count;
5666 _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc);
5667 _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0);
5668
5669 if (_sapp.sample_count > 1) {
5670 WGPUTextureDescriptor msaa_desc;
5671 _sapp_clear(&msaa_desc, sizeof(msaa_desc));
5672 msaa_desc.usage = WGPUTextureUsage_OutputAttachment;
5673 msaa_desc.dimension = WGPUTextureDimension_2D;
5674 msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width;
5675 msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height;
5676 msaa_desc.size.depth = 1;
5677 msaa_desc.arrayLayerCount = 1;
5678 msaa_desc.format = _sapp.emsc.wgpu.render_format;
5679 msaa_desc.mipLevelCount = 1;
5680 msaa_desc.sampleCount = _sapp.sample_count;
5681 _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc);
5682 _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0);
5683 }
5684}
5685
5686_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) {
5687 if (_sapp.emsc.wgpu.msaa_tex) {
5688 wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex);
5689 _sapp.emsc.wgpu.msaa_tex = 0;
5690 }
5691 if (_sapp.emsc.wgpu.msaa_view) {
5692 wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view);
5693 _sapp.emsc.wgpu.msaa_view = 0;
5694 }
5695 if (_sapp.emsc.wgpu.depth_stencil_tex) {
5696 wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex);
5697 _sapp.emsc.wgpu.depth_stencil_tex = 0;
5698 }
5699 if (_sapp.emsc.wgpu.depth_stencil_view) {
5700 wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view);
5701 _sapp.emsc.wgpu.depth_stencil_view = 0;
5702 }
5703}
5704
5705_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) {
5706 if (_sapp.emsc.wgpu.swapchain_view) {
5707 wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view);
5708 }
5709 _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain);
5710}
5711#endif
5712
5713_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) {
5714 emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb);
5715 emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb);
5716 emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb);
5717 emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb);
5718 emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb);
5719 emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb);
5720 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb);
5721 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb);
5722 emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb);
5723 emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb);
5724 emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb);
5725 emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb);
5726 emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb);
5727 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb);
5728 emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb);
5729 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_focus_cb);
5730 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_blur_cb);
5731 sapp_js_add_beforeunload_listener();
5732 if (_sapp.clipboard.enabled) {
5733 sapp_js_add_clipboard_listener();
5734 }
5735 if (_sapp.drop.enabled) {
5736 sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
5737 }
5738 #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3)
5739 emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb);
5740 emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb);
5741 #endif
5742}
5743
5744_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() {
5745 emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0);
5746 emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0);
5747 emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0);
5748 emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0);
5749 emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0);
5750 emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0);
5751 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0);
5752 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0);
5753 emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0);
5754 emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0);
5755 emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0);
5756 emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0);
5757 emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0);
5758 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0);
5759 emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0);
5760 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0);
5761 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0);
5762 sapp_js_remove_beforeunload_listener();
5763 if (_sapp.clipboard.enabled) {
5764 sapp_js_remove_clipboard_listener();
5765 }
5766 if (_sapp.drop.enabled) {
5767 sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
5768 }
5769 #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3)
5770 emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0);
5771 emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0);
5772 #endif
5773}
5774
5775_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) {
5776 _SOKOL_UNUSED(userData);
5777 _sapp_timing_external(&_sapp.timing, time / 1000.0);
5778
5779 #if defined(SOKOL_WGPU)
5780 /*
5781 on WebGPU, the emscripten frame callback will already be called while
5782 the asynchronous WebGPU device and swapchain initialization is still
5783 in progress
5784 */
5785 switch (_sapp.emsc.wgpu.state) {
5786 case _SAPP_EMSC_WGPU_STATE_INITIAL:
5787 /* async JS init hasn't finished yet */
5788 break;
5789 case _SAPP_EMSC_WGPU_STATE_READY:
5790 /* perform post-async init stuff */
5791 _sapp_emsc_wgpu_surfaces_create();
5792 _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING;
5793 break;
5794 case _SAPP_EMSC_WGPU_STATE_RUNNING:
5795 /* a regular frame */
5796 _sapp_emsc_wgpu_next_frame();
5797 _sapp_frame();
5798 break;
5799 }
5800 #else
5801 /* WebGL code path */
5802 _sapp_frame();
5803 #endif
5804
5805 /* quit-handling */
5806 if (_sapp.quit_requested) {
5807 _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED);
5808 _sapp_call_event(&_sapp.event);
5809 if (_sapp.quit_requested) {
5810 _sapp.quit_ordered = true;
5811 }
5812 }
5813 if (_sapp.quit_ordered) {
5814 _sapp_emsc_unregister_eventhandlers();
5815 _sapp_call_cleanup();
5816 _sapp_discard_state();
5817 return EM_FALSE;
5818 }
5819 return EM_TRUE;
5820}
5821
5822_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) {
5823 _sapp_init_state(desc);
5824 sapp_js_init(&_sapp.html5_canvas_selector[1]);
5825 double w, h;
5826 if (_sapp.desc.html5_canvas_resize) {
5827 w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH);
5828 h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT);
5829 }
5830 else {
5831 emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h);
5832 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed);
5833 }
5834 if (_sapp.desc.high_dpi) {
5835 _sapp.dpi_scale = emscripten_get_device_pixel_ratio();
5836 }
5837 _sapp.window_width = (int)roundf(w);
5838 _sapp.window_height = (int)roundf(h);
5839 _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale);
5840 _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale);
5841 emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height);
5842 #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3)
5843 _sapp_emsc_webgl_init();
5844 #elif defined(SOKOL_WGPU)
5845 sapp_js_wgpu_init();
5846 #endif
5847 _sapp.valid = true;
5848 _sapp_emsc_register_eventhandlers();
5849 sapp_set_icon(&desc->icon);
5850
5851 /* start the frame loop */
5852 emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0);
5853
5854 /* NOT A BUG: do not call _sapp_discard_state() here, instead this is
5855 called in _sapp_emsc_frame() when the application is ordered to quit
5856 */
5857}
5858
5859#if !defined(SOKOL_NO_ENTRY)
5860int main(int argc, char* argv[]) {
5861 sapp_desc desc = sokol_main(argc, argv);
5862 _sapp_emsc_run(&desc);
5863 return 0;
5864}
5865#endif /* SOKOL_NO_ENTRY */
5866#endif /* _SAPP_EMSCRIPTEN */
5867
5868/*== MISC GL SUPPORT FUNCTIONS ================================================*/
5869#if defined(SOKOL_GLCORE33)
5870typedef struct {
5871 int red_bits;
5872 int green_bits;
5873 int blue_bits;
5874 int alpha_bits;
5875 int depth_bits;
5876 int stencil_bits;
5877 int samples;
5878 bool doublebuffer;
5879 uintptr_t handle;
5880} _sapp_gl_fbconfig;
5881
5882_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) {
5883 _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig));
5884 /* -1 means "don't care" */
5885 fbconfig->red_bits = -1;
5886 fbconfig->green_bits = -1;
5887 fbconfig->blue_bits = -1;
5888 fbconfig->alpha_bits = -1;
5889 fbconfig->depth_bits = -1;
5890 fbconfig->stencil_bits = -1;
5891 fbconfig->samples = -1;
5892}
5893
5894_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) {
5895 int missing, least_missing = 1000000;
5896 int color_diff, least_color_diff = 10000000;
5897 int extra_diff, least_extra_diff = 10000000;
5898 const _sapp_gl_fbconfig* current;
5899 const _sapp_gl_fbconfig* closest = 0;
5900 for (int i = 0; i < count; i++) {
5901 current = alternatives + i;
5902 if (desired->doublebuffer != current->doublebuffer) {
5903 continue;
5904 }
5905 missing = 0;
5906 if (desired->alpha_bits > 0 && current->alpha_bits == 0) {
5907 missing++;
5908 }
5909 if (desired->depth_bits > 0 && current->depth_bits == 0) {
5910 missing++;
5911 }
5912 if (desired->stencil_bits > 0 && current->stencil_bits == 0) {
5913 missing++;
5914 }
5915 if (desired->samples > 0 && current->samples == 0) {
5916 /* Technically, several multisampling buffers could be
5917 involved, but that's a lower level implementation detail and
5918 not important to us here, so we count them as one
5919 */
5920 missing++;
5921 }
5922
5923 /* These polynomials make many small channel size differences matter
5924 less than one large channel size difference
5925 Calculate color channel size difference value
5926 */
5927 color_diff = 0;
5928 if (desired->red_bits != -1) {
5929 color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits);
5930 }
5931 if (desired->green_bits != -1) {
5932 color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits);
5933 }
5934 if (desired->blue_bits != -1) {
5935 color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits);
5936 }
5937
5938 /* Calculate non-color channel size difference value */
5939 extra_diff = 0;
5940 if (desired->alpha_bits != -1) {
5941 extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits);
5942 }
5943 if (desired->depth_bits != -1) {
5944 extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits);
5945 }
5946 if (desired->stencil_bits != -1) {
5947 extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits);
5948 }
5949 if (desired->samples != -1) {
5950 extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples);
5951 }
5952
5953 /* Figure out if the current one is better than the best one found so far
5954 Least number of missing buffers is the most important heuristic,
5955 then color buffer size match and lastly size match for other buffers
5956 */
5957 if (missing < least_missing) {
5958 closest = current;
5959 }
5960 else if (missing == least_missing) {
5961 if ((color_diff < least_color_diff) ||
5962 (color_diff == least_color_diff && extra_diff < least_extra_diff))
5963 {
5964 closest = current;
5965 }
5966 }
5967 if (current == closest) {
5968 least_missing = missing;
5969 least_color_diff = color_diff;
5970 least_extra_diff = extra_diff;
5971 }
5972 }
5973 return closest;
5974}
5975#endif
5976
5977/*== WINDOWS DESKTOP and UWP====================================================*/
5978#if defined(_SAPP_WIN32) || defined(_SAPP_UWP)
5979_SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) {
5980 SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
5981 _sapp_clear(dst, (size_t)dst_num_bytes);
5982 const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t);
5983 const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0);
5984 if ((dst_needed > 0) && (dst_needed < dst_chars)) {
5985 MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars);
5986 return true;
5987 }
5988 else {
5989 /* input string doesn't fit into destination buffer */
5990 return false;
5991 }
5992}
5993
5994_SOKOL_PRIVATE void _sapp_win32_uwp_app_event(sapp_event_type type) {
5995 if (_sapp_events_enabled()) {
5996 _sapp_init_event(type);
5997 _sapp_call_event(&_sapp.event);
5998 }
5999}
6000
6001_SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) {
6002 /* same as GLFW */
6003 _sapp.keycodes[0x00B] = SAPP_KEYCODE_0;
6004 _sapp.keycodes[0x002] = SAPP_KEYCODE_1;
6005 _sapp.keycodes[0x003] = SAPP_KEYCODE_2;
6006 _sapp.keycodes[0x004] = SAPP_KEYCODE_3;
6007 _sapp.keycodes[0x005] = SAPP_KEYCODE_4;
6008 _sapp.keycodes[0x006] = SAPP_KEYCODE_5;
6009 _sapp.keycodes[0x007] = SAPP_KEYCODE_6;
6010 _sapp.keycodes[0x008] = SAPP_KEYCODE_7;
6011 _sapp.keycodes[0x009] = SAPP_KEYCODE_8;
6012 _sapp.keycodes[0x00A] = SAPP_KEYCODE_9;
6013 _sapp.keycodes[0x01E] = SAPP_KEYCODE_A;
6014 _sapp.keycodes[0x030] = SAPP_KEYCODE_B;
6015 _sapp.keycodes[0x02E] = SAPP_KEYCODE_C;
6016 _sapp.keycodes[0x020] = SAPP_KEYCODE_D;
6017 _sapp.keycodes[0x012] = SAPP_KEYCODE_E;
6018 _sapp.keycodes[0x021] = SAPP_KEYCODE_F;
6019 _sapp.keycodes[0x022] = SAPP_KEYCODE_G;
6020 _sapp.keycodes[0x023] = SAPP_KEYCODE_H;
6021 _sapp.keycodes[0x017] = SAPP_KEYCODE_I;
6022 _sapp.keycodes[0x024] = SAPP_KEYCODE_J;
6023 _sapp.keycodes[0x025] = SAPP_KEYCODE_K;
6024 _sapp.keycodes[0x026] = SAPP_KEYCODE_L;
6025 _sapp.keycodes[0x032] = SAPP_KEYCODE_M;
6026 _sapp.keycodes[0x031] = SAPP_KEYCODE_N;
6027 _sapp.keycodes[0x018] = SAPP_KEYCODE_O;
6028 _sapp.keycodes[0x019] = SAPP_KEYCODE_P;
6029 _sapp.keycodes[0x010] = SAPP_KEYCODE_Q;
6030 _sapp.keycodes[0x013] = SAPP_KEYCODE_R;
6031 _sapp.keycodes[0x01F] = SAPP_KEYCODE_S;
6032 _sapp.keycodes[0x014] = SAPP_KEYCODE_T;
6033 _sapp.keycodes[0x016] = SAPP_KEYCODE_U;
6034 _sapp.keycodes[0x02F] = SAPP_KEYCODE_V;
6035 _sapp.keycodes[0x011] = SAPP_KEYCODE_W;
6036 _sapp.keycodes[0x02D] = SAPP_KEYCODE_X;
6037 _sapp.keycodes[0x015] = SAPP_KEYCODE_Y;
6038 _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z;
6039 _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE;
6040 _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH;
6041 _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA;
6042 _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL;
6043 _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT;
6044 _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET;
6045 _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS;
6046 _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD;
6047 _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET;
6048 _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON;
6049 _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH;
6050 _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2;
6051 _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE;
6052 _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE;
6053 _sapp.keycodes[0x14F] = SAPP_KEYCODE_END;
6054 _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER;
6055 _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE;
6056 _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME;
6057 _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT;
6058 _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU;
6059 _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN;
6060 _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP;
6061 _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE;
6062 _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE;
6063 _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE;
6064 _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB;
6065 _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK;
6066 _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK;
6067 _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK;
6068 _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1;
6069 _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2;
6070 _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3;
6071 _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4;
6072 _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5;
6073 _sapp.keycodes[0x040] = SAPP_KEYCODE_F6;
6074 _sapp.keycodes[0x041] = SAPP_KEYCODE_F7;
6075 _sapp.keycodes[0x042] = SAPP_KEYCODE_F8;
6076 _sapp.keycodes[0x043] = SAPP_KEYCODE_F9;
6077 _sapp.keycodes[0x044] = SAPP_KEYCODE_F10;
6078 _sapp.keycodes[0x057] = SAPP_KEYCODE_F11;
6079 _sapp.keycodes[0x058] = SAPP_KEYCODE_F12;
6080 _sapp.keycodes[0x064] = SAPP_KEYCODE_F13;
6081 _sapp.keycodes[0x065] = SAPP_KEYCODE_F14;
6082 _sapp.keycodes[0x066] = SAPP_KEYCODE_F15;
6083 _sapp.keycodes[0x067] = SAPP_KEYCODE_F16;
6084 _sapp.keycodes[0x068] = SAPP_KEYCODE_F17;
6085 _sapp.keycodes[0x069] = SAPP_KEYCODE_F18;
6086 _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19;
6087 _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20;
6088 _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21;
6089 _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22;
6090 _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23;
6091 _sapp.keycodes[0x076] = SAPP_KEYCODE_F24;
6092 _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT;
6093 _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL;
6094 _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT;
6095 _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER;
6096 _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN;
6097 _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT;
6098 _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL;
6099 _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT;
6100 _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER;
6101 _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN;
6102 _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT;
6103 _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT;
6104 _sapp.keycodes[0x148] = SAPP_KEYCODE_UP;
6105 _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0;
6106 _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1;
6107 _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2;
6108 _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3;
6109 _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4;
6110 _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5;
6111 _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6;
6112 _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7;
6113 _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8;
6114 _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9;
6115 _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD;
6116 _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL;
6117 _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE;
6118 _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER;
6119 _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY;
6120 _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT;
6121}
6122#endif // _SAPP_WIN32 || _SAPP_UWP
6123
6124/*== WINDOWS DESKTOP===========================================================*/
6125#if defined(_SAPP_WIN32)
6126
6127#if defined(SOKOL_D3D11)
6128
6129#if defined(__cplusplus)
6130#define _sapp_d3d11_Release(self) (self)->Release()
6131#define _sapp_win32_refiid(iid) iid
6132#else
6133#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self)
6134#define _sapp_win32_refiid(iid) &iid
6135#endif
6136
6137#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; }
6138
6139
6140static const IID _sapp_IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89, {0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c} };
6141static const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} };
6142static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} };
6143
6144static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) {
6145 #if defined(__cplusplus)
6146 return self->GetBuffer(Buffer, riid, ppSurface);
6147 #else
6148 return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface);
6149 #endif
6150}
6151
6152static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) {
6153 #if defined(__cplusplus)
6154 return self->QueryInterface(riid, ppvObject);
6155 #else
6156 return self->lpVtbl->QueryInterface(self, riid, ppvObject);
6157 #endif
6158}
6159
6160static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) {
6161 #if defined(__cplusplus)
6162 return self->CreateRenderTargetView(pResource, pDesc, ppRTView);
6163 #else
6164 return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView);
6165 #endif
6166}
6167
6168static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) {
6169 #if defined(__cplusplus)
6170 return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D);
6171 #else
6172 return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D);
6173 #endif
6174}
6175
6176static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) {
6177 #if defined(__cplusplus)
6178 return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView);
6179 #else
6180 return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView);
6181 #endif
6182}
6183
6184static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) {
6185 #if defined(__cplusplus)
6186 self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format);
6187 #else
6188 self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format);
6189 #endif
6190}
6191
6192static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) {
6193 #if defined(__cplusplus)
6194 return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags);
6195 #else
6196 return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags);
6197 #endif
6198}
6199
6200static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) {
6201 #if defined(__cplusplus)
6202 return self->Present(SyncInterval, Flags);
6203 #else
6204 return self->lpVtbl->Present(self, SyncInterval, Flags);
6205 #endif
6206}
6207
6208static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) {
6209 #if defined(__cplusplus)
6210 return self->GetFrameStatistics(pStats);
6211 #else
6212 return self->lpVtbl->GetFrameStatistics(self, pStats);
6213 #endif
6214}
6215
6216static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) {
6217 #if defined(__cplusplus)
6218 return self->SetMaximumFrameLatency(MaxLatency);
6219 #else
6220 return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency);
6221 #endif
6222}
6223
6224static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) {
6225 #if defined(__cplusplus)
6226 return self->GetAdapter(pAdapter);
6227 #else
6228 return self->lpVtbl->GetAdapter(self, pAdapter);
6229 #endif
6230}
6231
6232static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) {
6233 #if defined(__cplusplus)
6234 return self->GetParent(riid, ppParent);
6235 #else
6236 return self->lpVtbl->GetParent(self, riid, ppParent);
6237 #endif
6238}
6239
6240static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) {
6241 #if defined(__cplusplus)
6242 return self->MakeWindowAssociation(WindowHandle, Flags);
6243 #else
6244 return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags);
6245 #endif
6246}
6247
6248_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) {
6249 DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc;
6250 sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width;
6251 sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height;
6252 sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
6253 sc_desc->BufferDesc.RefreshRate.Numerator = 60;
6254 sc_desc->BufferDesc.RefreshRate.Denominator = 1;
6255 sc_desc->OutputWindow = _sapp.win32.hwnd;
6256 sc_desc->Windowed = true;
6257 if (_sapp.win32.is_win10_or_greater) {
6258 sc_desc->BufferCount = 2;
6259 sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD;
6260 _sapp.d3d11.use_dxgi_frame_stats = true;
6261 }
6262 else {
6263 sc_desc->BufferCount = 1;
6264 sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
6265 _sapp.d3d11.use_dxgi_frame_stats = false;
6266 }
6267 sc_desc->SampleDesc.Count = 1;
6268 sc_desc->SampleDesc.Quality = 0;
6269 sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
6270 UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
6271 #if defined(SOKOL_DEBUG)
6272 create_flags |= D3D11_CREATE_DEVICE_DEBUG;
6273 #endif
6274 D3D_FEATURE_LEVEL feature_level;
6275 HRESULT hr = D3D11CreateDeviceAndSwapChain(
6276 NULL, /* pAdapter (use default) */
6277 D3D_DRIVER_TYPE_HARDWARE, /* DriverType */
6278 NULL, /* Software */
6279 create_flags, /* Flags */
6280 NULL, /* pFeatureLevels */
6281 0, /* FeatureLevels */
6282 D3D11_SDK_VERSION, /* SDKVersion */
6283 sc_desc, /* pSwapChainDesc */
6284 &_sapp.d3d11.swap_chain, /* ppSwapChain */
6285 &_sapp.d3d11.device, /* ppDevice */
6286 &feature_level, /* pFeatureLevel */
6287 &_sapp.d3d11.device_context); /* ppImmediateContext */
6288 _SOKOL_UNUSED(hr);
6289 #if defined(SOKOL_DEBUG)
6290 if (!SUCCEEDED(hr)) {
6291 // if initialization with D3D11_CREATE_DEVICE_DEBUG failes, this could be because the
6292 // 'D3D11 debug layer' stopped working, indicated by the error message:
6293 // ===
6294 // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system.
6295 // These flags must be removed, or the Windows 10 SDK must be installed.
6296 // Flags include: D3D11_CREATE_DEVICE_DEBUG
6297 // ===
6298 //
6299 // ...just retry with the DEBUG flag switched off
6300 SAPP_LOG("sokol_app.h: D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.\n");
6301 create_flags &= ~D3D11_CREATE_DEVICE_DEBUG;
6302 hr = D3D11CreateDeviceAndSwapChain(
6303 NULL, /* pAdapter (use default) */
6304 D3D_DRIVER_TYPE_HARDWARE, /* DriverType */
6305 NULL, /* Software */
6306 create_flags, /* Flags */
6307 NULL, /* pFeatureLevels */
6308 0, /* FeatureLevels */
6309 D3D11_SDK_VERSION, /* SDKVersion */
6310 sc_desc, /* pSwapChainDesc */
6311 &_sapp.d3d11.swap_chain, /* ppSwapChain */
6312 &_sapp.d3d11.device, /* ppDevice */
6313 &feature_level, /* pFeatureLevel */
6314 &_sapp.d3d11.device_context); /* ppImmediateContext */
6315 }
6316 #endif
6317 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context);
6318
6319 // mimimize frame latency, disable Alt-Enter
6320 hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device);
6321 if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) {
6322 _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1);
6323 IDXGIAdapter* dxgi_adapter = 0;
6324 hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter);
6325 if (SUCCEEDED(hr) && dxgi_adapter) {
6326 IDXGIFactory* dxgi_factory = 0;
6327 hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory);
6328 if (SUCCEEDED(hr)) {
6329 _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN);
6330 _SAPP_SAFE_RELEASE(dxgi_factory);
6331 }
6332 else {
6333 SAPP_LOG("sokol_app.h: could not obtain IDXGIFactory object.\n");
6334 }
6335 _SAPP_SAFE_RELEASE(dxgi_adapter);
6336 }
6337 else {
6338 SAPP_LOG("sokol_app.h: could not obtain IDXGIAdapter object.\n");
6339 }
6340 }
6341 else {
6342 SAPP_LOG("sokol_app.h: could not obtain IDXGIDevice1 interface\n");
6343 }
6344}
6345
6346_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) {
6347 _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain);
6348 _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device);
6349 _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context);
6350 _SAPP_SAFE_RELEASE(_sapp.d3d11.device);
6351}
6352
6353_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) {
6354 SOKOL_ASSERT(0 == _sapp.d3d11.rt);
6355 SOKOL_ASSERT(0 == _sapp.d3d11.rtv);
6356 SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt);
6357 SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv);
6358 SOKOL_ASSERT(0 == _sapp.d3d11.ds);
6359 SOKOL_ASSERT(0 == _sapp.d3d11.dsv);
6360
6361 HRESULT hr;
6362
6363 /* view for the swapchain-created framebuffer */
6364 hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt);
6365 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt);
6366 hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv);
6367 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv);
6368
6369 /* common desc for MSAA and depth-stencil texture */
6370 D3D11_TEXTURE2D_DESC tex_desc;
6371 _sapp_clear(&tex_desc, sizeof(tex_desc));
6372 tex_desc.Width = (UINT)_sapp.framebuffer_width;
6373 tex_desc.Height = (UINT)_sapp.framebuffer_height;
6374 tex_desc.MipLevels = 1;
6375 tex_desc.ArraySize = 1;
6376 tex_desc.Usage = D3D11_USAGE_DEFAULT;
6377 tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET;
6378 tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count;
6379 tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0);
6380
6381 /* create MSAA texture and view if antialiasing requested */
6382 if (_sapp.sample_count > 1) {
6383 tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
6384 hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt);
6385 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt);
6386 hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv);
6387 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv);
6388 }
6389
6390 /* texture and view for the depth-stencil-surface */
6391 tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
6392 tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
6393 hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds);
6394 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds);
6395 hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv);
6396 SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv);
6397}
6398
6399_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) {
6400 _SAPP_SAFE_RELEASE(_sapp.d3d11.rt);
6401 _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv);
6402 _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt);
6403 _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv);
6404 _SAPP_SAFE_RELEASE(_sapp.d3d11.ds);
6405 _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv);
6406}
6407
6408_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) {
6409 if (_sapp.d3d11.swap_chain) {
6410 _sapp_d3d11_destroy_default_render_target();
6411 _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0);
6412 _sapp_d3d11_create_default_render_target();
6413 }
6414}
6415
6416_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) {
6417 /* do MSAA resolve if needed */
6418 if (_sapp.sample_count > 1) {
6419 SOKOL_ASSERT(_sapp.d3d11.rt);
6420 SOKOL_ASSERT(_sapp.d3d11.msaa_rt);
6421 _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM);
6422 }
6423 UINT flags = 0;
6424 if (_sapp.win32.is_win10_or_greater && do_not_wait) {
6425 /* this hack/workaround somewhat improves window-movement and -sizing
6426 responsiveness when rendering is controlled via WM_TIMER during window
6427 move and resize on NVIDIA cards on Win10 with recent drivers.
6428 */
6429 flags = DXGI_PRESENT_DO_NOT_WAIT;
6430 }
6431 _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags);
6432}
6433
6434#endif /* SOKOL_D3D11 */
6435
6436#if defined(SOKOL_GLCORE33)
6437_SOKOL_PRIVATE void _sapp_wgl_init(void) {
6438 _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll");
6439 if (!_sapp.wgl.opengl32) {
6440 _sapp_fail("Failed to load opengl32.dll\n");
6441 }
6442 SOKOL_ASSERT(_sapp.wgl.opengl32);
6443 _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext");
6444 SOKOL_ASSERT(_sapp.wgl.CreateContext);
6445 _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext");
6446 SOKOL_ASSERT(_sapp.wgl.DeleteContext);
6447 _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress");
6448 SOKOL_ASSERT(_sapp.wgl.GetProcAddress);
6449 _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC");
6450 SOKOL_ASSERT(_sapp.wgl.GetCurrentDC);
6451 _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent");
6452 SOKOL_ASSERT(_sapp.wgl.MakeCurrent);
6453
6454 _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW,
6455 L"SOKOLAPP",
6456 L"sokol-app message window",
6457 WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
6458 0, 0, 1, 1,
6459 NULL, NULL,
6460 GetModuleHandleW(NULL),
6461 NULL);
6462 if (!_sapp.wgl.msg_hwnd) {
6463 _sapp_fail("Win32: failed to create helper window!\n");
6464 }
6465 SOKOL_ASSERT(_sapp.wgl.msg_hwnd);
6466 ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE);
6467 MSG msg;
6468 while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) {
6469 TranslateMessage(&msg);
6470 DispatchMessageW(&msg);
6471 }
6472 _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd);
6473 if (!_sapp.wgl.msg_dc) {
6474 _sapp_fail("Win32: failed to obtain helper window DC!\n");
6475 }
6476}
6477
6478_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) {
6479 SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd);
6480 DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0;
6481 FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0;
6482}
6483
6484_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) {
6485 SOKOL_ASSERT(ext && extensions);
6486 const char* start = extensions;
6487 while (true) {
6488 const char* where = strstr(start, ext);
6489 if (!where) {
6490 return false;
6491 }
6492 const char* terminator = where + strlen(ext);
6493 if ((where == start) || (*(where - 1) == ' ')) {
6494 if (*terminator == ' ' || *terminator == '\0') {
6495 break;
6496 }
6497 }
6498 start = terminator;
6499 }
6500 return true;
6501}
6502
6503_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) {
6504 SOKOL_ASSERT(ext);
6505 if (_sapp.wgl.GetExtensionsStringEXT) {
6506 const char* extensions = _sapp.wgl.GetExtensionsStringEXT();
6507 if (extensions) {
6508 if (_sapp_wgl_has_ext(ext, extensions)) {
6509 return true;
6510 }
6511 }
6512 }
6513 if (_sapp.wgl.GetExtensionsStringARB) {
6514 const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC());
6515 if (extensions) {
6516 if (_sapp_wgl_has_ext(ext, extensions)) {
6517 return true;
6518 }
6519 }
6520 }
6521 return false;
6522}
6523
6524_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) {
6525 SOKOL_ASSERT(_sapp.wgl.msg_dc);
6526 PIXELFORMATDESCRIPTOR pfd;
6527 _sapp_clear(&pfd, sizeof(pfd));
6528 pfd.nSize = sizeof(pfd);
6529 pfd.nVersion = 1;
6530 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
6531 pfd.iPixelType = PFD_TYPE_RGBA;
6532 pfd.cColorBits = 24;
6533 if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) {
6534 _sapp_fail("WGL: failed to set pixel format for dummy context\n");
6535 }
6536 HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc);
6537 if (!rc) {
6538 _sapp_fail("WGL: Failed to create dummy context\n");
6539 }
6540 if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) {
6541 _sapp_fail("WGL: Failed to make context current\n");
6542 }
6543 _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT");
6544 _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB");
6545 _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB");
6546 _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT");
6547 _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB");
6548 _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample");
6549 _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context");
6550 _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile");
6551 _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control");
6552 _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format");
6553 _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0);
6554 _sapp.wgl.DeleteContext(rc);
6555}
6556
6557_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) {
6558 SOKOL_ASSERT(_sapp.wgl.arb_pixel_format);
6559 int value = 0;
6560 if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) {
6561 _sapp_fail("WGL: Failed to retrieve pixel format attribute\n");
6562 }
6563 return value;
6564}
6565
6566_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) {
6567 SOKOL_ASSERT(_sapp.win32.dc);
6568 SOKOL_ASSERT(_sapp.wgl.arb_pixel_format);
6569 const _sapp_gl_fbconfig* closest;
6570
6571 int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB);
6572 _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig));
6573 SOKOL_ASSERT(usable_configs);
6574 int usable_count = 0;
6575 for (int i = 0; i < native_count; i++) {
6576 const int n = i + 1;
6577 _sapp_gl_fbconfig* u = usable_configs + usable_count;
6578 _sapp_gl_init_fbconfig(u);
6579 if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) {
6580 continue;
6581 }
6582 if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) {
6583 continue;
6584 }
6585 if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) {
6586 continue;
6587 }
6588 u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB);
6589 u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB);
6590 u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB);
6591 u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB);
6592 u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB);
6593 u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB);
6594 if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) {
6595 u->doublebuffer = true;
6596 }
6597 if (_sapp.wgl.arb_multisample) {
6598 u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB);
6599 }
6600 u->handle = (uintptr_t)n;
6601 usable_count++;
6602 }
6603 SOKOL_ASSERT(usable_count > 0);
6604 _sapp_gl_fbconfig desired;
6605 _sapp_gl_init_fbconfig(&desired);
6606 desired.red_bits = 8;
6607 desired.green_bits = 8;
6608 desired.blue_bits = 8;
6609 desired.alpha_bits = 8;
6610 desired.depth_bits = 24;
6611 desired.stencil_bits = 8;
6612 desired.doublebuffer = true;
6613 desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0;
6614 closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count);
6615 int pixel_format = 0;
6616 if (closest) {
6617 pixel_format = (int) closest->handle;
6618 }
6619 _sapp_free(usable_configs);
6620 return pixel_format;
6621}
6622
6623_SOKOL_PRIVATE void _sapp_wgl_create_context(void) {
6624 int pixel_format = _sapp_wgl_find_pixel_format();
6625 if (0 == pixel_format) {
6626 _sapp_fail("WGL: Didn't find matching pixel format.\n");
6627 }
6628 PIXELFORMATDESCRIPTOR pfd;
6629 if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) {
6630 _sapp_fail("WGL: Failed to retrieve PFD for selected pixel format!\n");
6631 }
6632 if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) {
6633 _sapp_fail("WGL: Failed to set selected pixel format!\n");
6634 }
6635 if (!_sapp.wgl.arb_create_context) {
6636 _sapp_fail("WGL: ARB_create_context required!\n");
6637 }
6638 if (!_sapp.wgl.arb_create_context_profile) {
6639 _sapp_fail("WGL: ARB_create_context_profile required!\n");
6640 }
6641 const int attrs[] = {
6642 WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version,
6643 WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version,
6644 WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
6645 WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
6646 0, 0
6647 };
6648 _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs);
6649 if (!_sapp.wgl.gl_ctx) {
6650 const DWORD err = GetLastError();
6651 if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) {
6652 _sapp_fail("WGL: Driver does not support OpenGL version 3.3\n");
6653 }
6654 else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) {
6655 _sapp_fail("WGL: Driver does not support the requested OpenGL profile");
6656 }
6657 else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) {
6658 _sapp_fail("WGL: The share context is not compatible with the requested context");
6659 }
6660 else {
6661 _sapp_fail("WGL: Failed to create OpenGL context");
6662 }
6663 }
6664 _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx);
6665 if (_sapp.wgl.ext_swap_control) {
6666 /* FIXME: DwmIsCompositionEnabled() (see GLFW) */
6667 _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval);
6668 }
6669}
6670
6671_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) {
6672 SOKOL_ASSERT(_sapp.wgl.gl_ctx);
6673 _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx);
6674 _sapp.wgl.gl_ctx = 0;
6675}
6676
6677_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) {
6678 SOKOL_ASSERT(_sapp.win32.dc);
6679 /* FIXME: DwmIsCompositionEnabled? (see GLFW) */
6680 SwapBuffers(_sapp.win32.dc);
6681}
6682#endif /* SOKOL_GLCORE33 */
6683
6684_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) {
6685 SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
6686 _sapp_clear(dst, (size_t)dst_num_bytes);
6687 const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
6688 if (bytes_needed <= dst_num_bytes) {
6689 WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL);
6690 return true;
6691 }
6692 else {
6693 return false;
6694 }
6695}
6696
6697/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */
6698_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) {
6699 RECT rect;
6700 if (GetClientRect(_sapp.win32.hwnd, &rect)) {
6701 float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale;
6702 float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale;
6703 _sapp.window_width = (int)roundf(window_width);
6704 _sapp.window_height = (int)roundf(window_height);
6705 int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale);
6706 int fb_height = (int)roundf(window_height * _sapp.win32.dpi.content_scale);
6707 /* prevent a framebuffer size of 0 when window is minimized */
6708 if (0 == fb_width) {
6709 fb_width = 1;
6710 }
6711 if (0 == fb_height) {
6712 fb_height = 1;
6713 }
6714 if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) {
6715 _sapp.framebuffer_width = fb_width;
6716 _sapp.framebuffer_height = fb_height;
6717 return true;
6718 }
6719 }
6720 else {
6721 _sapp.window_width = _sapp.window_height = 1;
6722 _sapp.framebuffer_width = _sapp.framebuffer_height = 1;
6723 }
6724 return false;
6725}
6726
6727_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) {
6728 HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST);
6729 MONITORINFO minfo;
6730 _sapp_clear(&minfo, sizeof(minfo));
6731 minfo.cbSize = sizeof(MONITORINFO);
6732 GetMonitorInfo(monitor, &minfo);
6733 const RECT mr = minfo.rcMonitor;
6734 const int monitor_w = mr.right - mr.left;
6735 const int monitor_h = mr.bottom - mr.top;
6736
6737 const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
6738 DWORD win_style;
6739 RECT rect = { 0, 0, 0, 0 };
6740
6741 _sapp.fullscreen = fullscreen;
6742 if (!_sapp.fullscreen) {
6743 win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX;
6744 rect = _sapp.win32.stored_window_rect;
6745 }
6746 else {
6747 GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect);
6748 win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE;
6749 rect.left = mr.left;
6750 rect.top = mr.top;
6751 rect.right = rect.left + monitor_w;
6752 rect.bottom = rect.top + monitor_h;
6753 AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style);
6754 }
6755 const int win_w = rect.right - rect.left;
6756 const int win_h = rect.bottom - rect.top;
6757 const int win_x = rect.left;
6758 const int win_y = rect.top;
6759 SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style);
6760 SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED);
6761}
6762
6763_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) {
6764 _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW);
6765}
6766
6767_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) {
6768 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
6769 // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined
6770 // before windows.h is included, but we can't guarantee that because
6771 // the sokol_app.h implementation may be included with other implementations
6772 // in the same compilation unit
6773 int id = 0;
6774 switch (cursor) {
6775 case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL
6776 case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM
6777 case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS
6778 case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND
6779 case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE
6780 case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS
6781 case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE
6782 case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW
6783 case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL
6784 case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO
6785 default: break;
6786 }
6787 if (id != 0) {
6788 _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED);
6789 }
6790 // fallback: default cursor
6791 if (0 == _sapp.win32.cursors[cursor]) {
6792 // 32512 => IDC_ARROW
6793 _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512));
6794 }
6795 SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]);
6796}
6797
6798_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) {
6799 for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) {
6800 _sapp_win32_init_cursor((sapp_mouse_cursor)i);
6801 }
6802}
6803
6804_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) {
6805 POINT pos;
6806 if (!GetCursorPos(&pos)) {
6807 return false;
6808 }
6809 if (WindowFromPoint(pos) != _sapp.win32.hwnd) {
6810 return false;
6811 }
6812 RECT area;
6813 GetClientRect(_sapp.win32.hwnd, &area);
6814 ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left);
6815 ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right);
6816 return PtInRect(&area, pos) == TRUE;
6817}
6818
6819_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) {
6820 // NOTE: when called from WM_SETCURSOR, the area test would be redundant
6821 if (!skip_area_test) {
6822 if (!_sapp_win32_cursor_in_content_area()) {
6823 return;
6824 }
6825 }
6826 if (!shown) {
6827 SetCursor(NULL);
6828 }
6829 else {
6830 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
6831 SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]);
6832 SetCursor(_sapp.win32.cursors[cursor]);
6833 }
6834}
6835
6836_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) {
6837 if (0 == _sapp.win32.mouse_capture_mask) {
6838 SetCapture(_sapp.win32.hwnd);
6839 }
6840 _sapp.win32.mouse_capture_mask |= btn_mask;
6841}
6842
6843_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) {
6844 if (0 != _sapp.win32.mouse_capture_mask) {
6845 _sapp.win32.mouse_capture_mask &= ~btn_mask;
6846 if (0 == _sapp.win32.mouse_capture_mask) {
6847 ReleaseCapture();
6848 }
6849 }
6850}
6851
6852_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) {
6853 if (lock == _sapp.mouse.locked) {
6854 return;
6855 }
6856 _sapp.mouse.dx = 0.0f;
6857 _sapp.mouse.dy = 0.0f;
6858 _sapp.mouse.locked = lock;
6859 _sapp_win32_release_mouse(0xFF);
6860 if (_sapp.mouse.locked) {
6861 /* store the current mouse position, so it can be restored when unlocked */
6862 POINT pos;
6863 BOOL res = GetCursorPos(&pos);
6864 SOKOL_ASSERT(res); _SOKOL_UNUSED(res);
6865 _sapp.win32.mouse_locked_x = pos.x;
6866 _sapp.win32.mouse_locked_y = pos.y;
6867
6868 /* while the mouse is locked, make the mouse cursor invisible and
6869 confine the mouse movement to a small rectangle inside our window
6870 (so that we dont miss any mouse up events)
6871 */
6872 RECT client_rect = {
6873 _sapp.win32.mouse_locked_x,
6874 _sapp.win32.mouse_locked_y,
6875 _sapp.win32.mouse_locked_x,
6876 _sapp.win32.mouse_locked_y
6877 };
6878 ClipCursor(&client_rect);
6879
6880 /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */
6881 ShowCursor(FALSE);
6882
6883 /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */
6884 const RAWINPUTDEVICE rid = {
6885 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC
6886 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE
6887 0, // dwFlags
6888 _sapp.win32.hwnd // hwndTarget
6889 };
6890 if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
6891 SAPP_LOG("RegisterRawInputDevices() failed (on mouse lock).\n");
6892 }
6893 /* in case the raw mouse device only supports absolute position reporting,
6894 we need to skip the dx/dy compution for the first WM_INPUT event
6895 */
6896 _sapp.win32.raw_input_mousepos_valid = false;
6897 }
6898 else {
6899 /* disable raw input for mouse */
6900 const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
6901 if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
6902 SAPP_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n");
6903 }
6904
6905 /* let the mouse roam freely again */
6906 ClipCursor(NULL);
6907 ShowCursor(TRUE);
6908
6909 /* restore the 'pre-locked' mouse position */
6910 BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y);
6911 SOKOL_ASSERT(res); _SOKOL_UNUSED(res);
6912 }
6913}
6914
6915_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) {
6916 const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL);
6917 if (cur_monitor != _sapp.win32.hmonitor) {
6918 _sapp.win32.hmonitor = cur_monitor;
6919 return true;
6920 }
6921 else {
6922 return false;
6923 }
6924}
6925
6926_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) {
6927 uint32_t mods = 0;
6928 if (GetKeyState(VK_SHIFT) & (1<<15)) {
6929 mods |= SAPP_MODIFIER_SHIFT;
6930 }
6931 if (GetKeyState(VK_CONTROL) & (1<<15)) {
6932 mods |= SAPP_MODIFIER_CTRL;
6933 }
6934 if (GetKeyState(VK_MENU) & (1<<15)) {
6935 mods |= SAPP_MODIFIER_ALT;
6936 }
6937 if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) {
6938 mods |= SAPP_MODIFIER_SUPER;
6939 }
6940 const bool swapped = (TRUE == GetSystemMetrics(SM_SWAPBUTTON));
6941 if (GetAsyncKeyState(VK_LBUTTON)) {
6942 mods |= swapped ? SAPP_MODIFIER_RMB : SAPP_MODIFIER_LMB;
6943 }
6944 if (GetAsyncKeyState(VK_RBUTTON)) {
6945 mods |= swapped ? SAPP_MODIFIER_LMB : SAPP_MODIFIER_RMB;
6946 }
6947 if (GetAsyncKeyState(VK_MBUTTON)) {
6948 mods |= SAPP_MODIFIER_MMB;
6949 }
6950 return mods;
6951}
6952
6953_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) {
6954 if (_sapp_events_enabled()) {
6955 _sapp_init_event(type);
6956 _sapp.event.modifiers = _sapp_win32_mods();
6957 _sapp.event.mouse_button = btn;
6958 _sapp_call_event(&_sapp.event);
6959 }
6960}
6961
6962_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) {
6963 if (_sapp_events_enabled()) {
6964 _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL);
6965 _sapp.event.modifiers = _sapp_win32_mods();
6966 _sapp.event.scroll_x = -x / 30.0f;
6967 _sapp.event.scroll_y = y / 30.0f;
6968 _sapp_call_event(&_sapp.event);
6969 }
6970}
6971
6972_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) {
6973 if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) {
6974 _sapp_init_event(type);
6975 _sapp.event.modifiers = _sapp_win32_mods();
6976 _sapp.event.key_code = _sapp.keycodes[vk];
6977 _sapp.event.key_repeat = repeat;
6978 _sapp_call_event(&_sapp.event);
6979 /* check if a CLIPBOARD_PASTED event must be sent too */
6980 if (_sapp.clipboard.enabled &&
6981 (type == SAPP_EVENTTYPE_KEY_DOWN) &&
6982 (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) &&
6983 (_sapp.event.key_code == SAPP_KEYCODE_V))
6984 {
6985 _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
6986 _sapp_call_event(&_sapp.event);
6987 }
6988 }
6989}
6990
6991_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) {
6992 if (_sapp_events_enabled() && (c >= 32)) {
6993 _sapp_init_event(SAPP_EVENTTYPE_CHAR);
6994 _sapp.event.modifiers = _sapp_win32_mods();
6995 _sapp.event.char_code = c;
6996 _sapp.event.key_repeat = repeat;
6997 _sapp_call_event(&_sapp.event);
6998 }
6999}
7000
7001_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) {
7002 /* called on WM_DPICHANGED, which will only be sent to the application
7003 if sapp_desc.high_dpi is true and the Windows version is recent enough
7004 to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
7005 */
7006 SOKOL_ASSERT(_sapp.desc.high_dpi);
7007 HINSTANCE user32 = LoadLibraryA("user32.dll");
7008 if (!user32) {
7009 return;
7010 }
7011 typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd);
7012 GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow");
7013 if (fn_getdpiforwindow) {
7014 UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd);
7015 // NOTE: for high-dpi apps, mouse_scale remains one
7016 _sapp.win32.dpi.window_scale = (float)dpix / 96.0f;
7017 _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale;
7018 _sapp.dpi_scale = _sapp.win32.dpi.window_scale;
7019 SetWindowPos(hWnd, 0,
7020 proposed_win_rect->left,
7021 proposed_win_rect->top,
7022 proposed_win_rect->right - proposed_win_rect->left,
7023 proposed_win_rect->bottom - proposed_win_rect->top,
7024 SWP_NOZORDER | SWP_NOACTIVATE);
7025 }
7026 FreeLibrary(user32);
7027}
7028
7029_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) {
7030 if (!_sapp.drop.enabled) {
7031 return;
7032 }
7033 _sapp_clear_drop_buffer();
7034 bool drop_failed = false;
7035 const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0);
7036 _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count;
7037 for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) {
7038 const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1;
7039 WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR));
7040 DragQueryFileW(hdrop, i, buffer, num_chars);
7041 if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) {
7042 SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
7043 drop_failed = true;
7044 }
7045 _sapp_free(buffer);
7046 }
7047 DragFinish(hdrop);
7048 if (!drop_failed) {
7049 if (_sapp_events_enabled()) {
7050 _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED);
7051 _sapp_call_event(&_sapp.event);
7052 }
7053 }
7054 else {
7055 _sapp_clear_drop_buffer();
7056 _sapp.drop.num_files = 0;
7057 }
7058}
7059
7060_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) {
7061 #if defined(SOKOL_D3D11)
7062 // on D3D11, use the more precise DXGI timestamp
7063 if (_sapp.d3d11.use_dxgi_frame_stats) {
7064 DXGI_FRAME_STATISTICS dxgi_stats;
7065 _sapp_clear(&dxgi_stats, sizeof(dxgi_stats));
7066 HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats);
7067 if (SUCCEEDED(hr)) {
7068 if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) {
7069 if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) {
7070 _sapp_timing_discontinuity(&_sapp.timing);
7071 }
7072 _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount;
7073 LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime;
7074 const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart);
7075 _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0);
7076 }
7077 return;
7078 }
7079 }
7080 // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason
7081 _sapp_timing_measure(&_sapp.timing);
7082 #endif
7083 #if defined(SOKOL_GLCORE33)
7084 _sapp_timing_measure(&_sapp.timing);
7085 #endif
7086}
7087
7088_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
7089 if (!_sapp.win32.in_create_window) {
7090 switch (uMsg) {
7091 case WM_CLOSE:
7092 /* only give user a chance to intervene when sapp_quit() wasn't already called */
7093 if (!_sapp.quit_ordered) {
7094 /* if window should be closed and event handling is enabled, give user code
7095 a change to intervene via sapp_cancel_quit()
7096 */
7097 _sapp.quit_requested = true;
7098 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED);
7099 /* if user code hasn't intervened, quit the app */
7100 if (_sapp.quit_requested) {
7101 _sapp.quit_ordered = true;
7102 }
7103 }
7104 if (_sapp.quit_ordered) {
7105 PostQuitMessage(0);
7106 }
7107 return 0;
7108 case WM_SYSCOMMAND:
7109 switch (wParam & 0xFFF0) {
7110 case SC_SCREENSAVE:
7111 case SC_MONITORPOWER:
7112 if (_sapp.fullscreen) {
7113 /* disable screen saver and blanking in fullscreen mode */
7114 return 0;
7115 }
7116 break;
7117 case SC_KEYMENU:
7118 /* user trying to access menu via ALT */
7119 return 0;
7120 }
7121 break;
7122 case WM_ERASEBKGND:
7123 return 1;
7124 case WM_SIZE:
7125 {
7126 const bool iconified = wParam == SIZE_MINIMIZED;
7127 if (iconified != _sapp.win32.iconified) {
7128 _sapp.win32.iconified = iconified;
7129 if (iconified) {
7130 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_ICONIFIED);
7131 }
7132 else {
7133 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESTORED);
7134 }
7135 }
7136 }
7137 break;
7138 case WM_SETFOCUS:
7139 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_FOCUSED);
7140 break;
7141 case WM_KILLFOCUS:
7142 /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */
7143 if (_sapp.mouse.locked) {
7144 _sapp_win32_lock_mouse(false);
7145 }
7146 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UNFOCUSED);
7147 break;
7148 case WM_SETCURSOR:
7149 if (LOWORD(lParam) == HTCLIENT) {
7150 _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true);
7151 return TRUE;
7152 }
7153 break;
7154 case WM_DPICHANGED:
7155 {
7156 /* Update window's DPI and size if its moved to another monitor with a different DPI
7157 Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used.
7158 */
7159 _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam);
7160 break;
7161 }
7162 case WM_LBUTTONDOWN:
7163 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT);
7164 _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_LEFT);
7165 break;
7166 case WM_RBUTTONDOWN:
7167 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_RIGHT);
7168 _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_RIGHT);
7169 break;
7170 case WM_MBUTTONDOWN:
7171 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_MIDDLE);
7172 _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_MIDDLE);
7173 break;
7174 case WM_LBUTTONUP:
7175 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_LEFT);
7176 _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_LEFT);
7177 break;
7178 case WM_RBUTTONUP:
7179 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_RIGHT);
7180 _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_RIGHT);
7181 break;
7182 case WM_MBUTTONUP:
7183 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_MIDDLE);
7184 _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_MIDDLE);
7185 break;
7186 case WM_MOUSEMOVE:
7187 if (!_sapp.mouse.locked) {
7188 const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale;
7189 const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale;
7190 /* don't update dx/dy in the very first event */
7191 if (_sapp.mouse.pos_valid) {
7192 _sapp.mouse.dx = new_x - _sapp.mouse.x;
7193 _sapp.mouse.dy = new_y - _sapp.mouse.y;
7194 }
7195 _sapp.mouse.x = new_x;
7196 _sapp.mouse.y = new_y;
7197 _sapp.mouse.pos_valid = true;
7198 if (!_sapp.win32.mouse_tracked) {
7199 _sapp.win32.mouse_tracked = true;
7200 TRACKMOUSEEVENT tme;
7201 _sapp_clear(&tme, sizeof(tme));
7202 tme.cbSize = sizeof(tme);
7203 tme.dwFlags = TME_LEAVE;
7204 tme.hwndTrack = _sapp.win32.hwnd;
7205 TrackMouseEvent(&tme);
7206 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID);
7207 }
7208 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID);
7209 }
7210 break;
7211 case WM_INPUT:
7212 /* raw mouse input during mouse-lock */
7213 if (_sapp.mouse.locked) {
7214 HRAWINPUT ri = (HRAWINPUT) lParam;
7215 UINT size = sizeof(_sapp.win32.raw_input_data);
7216 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata
7217 if ((UINT)-1 == GetRawInputData(ri, RID_INPUT, &_sapp.win32.raw_input_data, &size, sizeof(RAWINPUTHEADER))) {
7218 SAPP_LOG("GetRawInputData() failed\n");
7219 break;
7220 }
7221 const RAWINPUT* raw_mouse_data = (const RAWINPUT*) &_sapp.win32.raw_input_data;
7222 if (raw_mouse_data->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
7223 /* mouse only reports absolute position
7224 NOTE: THIS IS UNTESTED, it's unclear from reading the
7225 Win32 RawInput docs under which circumstances absolute
7226 positions are sent.
7227 */
7228 if (_sapp.win32.raw_input_mousepos_valid) {
7229 LONG new_x = raw_mouse_data->data.mouse.lLastX;
7230 LONG new_y = raw_mouse_data->data.mouse.lLastY;
7231 _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x);
7232 _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y);
7233 _sapp.win32.raw_input_mousepos_x = new_x;
7234 _sapp.win32.raw_input_mousepos_y = new_y;
7235 _sapp.win32.raw_input_mousepos_valid = true;
7236 }
7237 }
7238 else {
7239 /* mouse reports movement delta (this seems to be the common case) */
7240 _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX;
7241 _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY;
7242 }
7243 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID);
7244 }
7245 break;
7246
7247 case WM_MOUSELEAVE:
7248 if (!_sapp.mouse.locked) {
7249 _sapp.win32.mouse_tracked = false;
7250 _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID);
7251 }
7252 break;
7253 case WM_MOUSEWHEEL:
7254 _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam)));
7255 break;
7256 case WM_MOUSEHWHEEL:
7257 _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f);
7258 break;
7259 case WM_CHAR:
7260 _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000));
7261 break;
7262 case WM_KEYDOWN:
7263 case WM_SYSKEYDOWN:
7264 _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000));
7265 break;
7266 case WM_KEYUP:
7267 case WM_SYSKEYUP:
7268 _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false);
7269 break;
7270 case WM_ENTERSIZEMOVE:
7271 SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL);
7272 break;
7273 case WM_EXITSIZEMOVE:
7274 KillTimer(_sapp.win32.hwnd, 1);
7275 break;
7276 case WM_TIMER:
7277 _sapp_win32_timing_measure();
7278 _sapp_frame();
7279 #if defined(SOKOL_D3D11)
7280 // present with DXGI_PRESENT_DO_NOT_WAIT
7281 _sapp_d3d11_present(true);
7282 #endif
7283 #if defined(SOKOL_GLCORE33)
7284 _sapp_wgl_swap_buffers();
7285 #endif
7286 /* NOTE: resizing the swap-chain during resize leads to a substantial
7287 memory spike (hundreds of megabytes for a few seconds).
7288
7289 if (_sapp_win32_update_dimensions()) {
7290 #if defined(SOKOL_D3D11)
7291 _sapp_d3d11_resize_default_render_target();
7292 #endif
7293 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED);
7294 }
7295 */
7296 break;
7297 case WM_NCLBUTTONDOWN:
7298 /* workaround for half-second pause when starting to move window
7299 see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/
7300 */
7301 if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) {
7302 POINT point;
7303 GetCursorPos(&point);
7304 ScreenToClient(_sapp.win32.hwnd, &point);
7305 PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16));
7306 }
7307 break;
7308 case WM_DROPFILES:
7309 _sapp_win32_files_dropped((HDROP)wParam);
7310 break;
7311 case WM_DISPLAYCHANGE:
7312 // refresh rate might have changed
7313 _sapp_timing_reset(&_sapp.timing);
7314 break;
7315
7316 default:
7317 break;
7318 }
7319 }
7320 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
7321}
7322
7323_SOKOL_PRIVATE void _sapp_win32_create_window(void) {
7324 WNDCLASSW wndclassw;
7325 _sapp_clear(&wndclassw, sizeof(wndclassw));
7326 wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
7327 wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc;
7328 wndclassw.hInstance = GetModuleHandleW(NULL);
7329 wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW);
7330 wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO);
7331 wndclassw.lpszClassName = L"SOKOLAPP";
7332 RegisterClassW(&wndclassw);
7333
7334 /* NOTE: regardless whether fullscreen is requested or not, a regular
7335 windowed-mode window will always be created first (however in hidden
7336 mode, so that no windowed-mode window pops up before the fullscreen window)
7337 */
7338 const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
7339 RECT rect = { 0, 0, 0, 0 };
7340 DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX;
7341 rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale);
7342 rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale);
7343 const bool use_default_width = 0 == _sapp.window_width;
7344 const bool use_default_height = 0 == _sapp.window_height;
7345 AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style);
7346 const int win_width = rect.right - rect.left;
7347 const int win_height = rect.bottom - rect.top;
7348 _sapp.win32.in_create_window = true;
7349 _sapp.win32.hwnd = CreateWindowExW(
7350 win_ex_style, // dwExStyle
7351 L"SOKOLAPP", // lpClassName
7352 _sapp.window_title_wide, // lpWindowName
7353 win_style, // dwStyle
7354 CW_USEDEFAULT, // X
7355 SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow!
7356 use_default_width ? CW_USEDEFAULT : win_width, // nWidth
7357 use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored)
7358 NULL, // hWndParent
7359 NULL, // hMenu
7360 GetModuleHandle(NULL), // hInstance
7361 NULL); // lParam
7362 _sapp.win32.in_create_window = false;
7363 _sapp.win32.dc = GetDC(_sapp.win32.hwnd);
7364 _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL);
7365 SOKOL_ASSERT(_sapp.win32.dc);
7366
7367 /* this will get the actual windowed-mode window size, if fullscreen
7368 is requested, the set_fullscreen function will then capture the
7369 current window rectangle, which then might be used later to
7370 restore the window position when switching back to windowed
7371 */
7372 _sapp_win32_update_dimensions();
7373 if (_sapp.fullscreen) {
7374 _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW);
7375 _sapp_win32_update_dimensions();
7376 }
7377 ShowWindow(_sapp.win32.hwnd, SW_SHOW);
7378 DragAcceptFiles(_sapp.win32.hwnd, 1);
7379}
7380
7381_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) {
7382 DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0;
7383 UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL));
7384}
7385
7386_SOKOL_PRIVATE void _sapp_win32_destroy_icons(void) {
7387 if (_sapp.win32.big_icon) {
7388 DestroyIcon(_sapp.win32.big_icon);
7389 _sapp.win32.big_icon = 0;
7390 }
7391 if (_sapp.win32.small_icon) {
7392 DestroyIcon(_sapp.win32.small_icon);
7393 _sapp.win32.small_icon = 0;
7394 }
7395}
7396
7397_SOKOL_PRIVATE void _sapp_win32_init_console(void) {
7398 if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) {
7399 BOOL con_valid = FALSE;
7400 if (_sapp.desc.win32_console_create) {
7401 con_valid = AllocConsole();
7402 }
7403 else if (_sapp.desc.win32_console_attach) {
7404 con_valid = AttachConsole(ATTACH_PARENT_PROCESS);
7405 }
7406 if (con_valid) {
7407 FILE* res_fp = 0;
7408 errno_t err;
7409 err = freopen_s(&res_fp, "CON", "w", stdout);
7410 (void)err;
7411 err = freopen_s(&res_fp, "CON", "w", stderr);
7412 (void)err;
7413 }
7414 }
7415 if (_sapp.desc.win32_console_utf8) {
7416 _sapp.win32.orig_codepage = GetConsoleOutputCP();
7417 SetConsoleOutputCP(CP_UTF8);
7418 }
7419}
7420
7421_SOKOL_PRIVATE void _sapp_win32_restore_console(void) {
7422 if (_sapp.desc.win32_console_utf8) {
7423 SetConsoleOutputCP(_sapp.win32.orig_codepage);
7424 }
7425}
7426
7427_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) {
7428
7429 DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T);
7430 typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void);
7431 typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703
7432 typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS);
7433 typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
7434
7435 SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0;
7436 SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0;
7437 GETDPIFORMONITOR_T fn_getdpiformonitor = 0;
7438 SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0;
7439
7440 HINSTANCE user32 = LoadLibraryA("user32.dll");
7441 if (user32) {
7442 fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware");
7443 fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext");
7444 }
7445 HINSTANCE shcore = LoadLibraryA("shcore.dll");
7446 if (shcore) {
7447 fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness");
7448 fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor");
7449 }
7450 /*
7451 NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext():
7452
7453 These are different attempts to get DPI handling on Windows right, from oldest
7454 to newest. SetProcessDpiAwarenessContext() is required for the new
7455 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method.
7456 */
7457 if (fn_setprocessdpiawareness) {
7458 if (_sapp.desc.high_dpi) {
7459 /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness,
7460 if that fails, fall back to system-dpi-awareness
7461 */
7462 _sapp.win32.dpi.aware = true;
7463 DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4;
7464 if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) {
7465 // fallback to system-dpi-aware
7466 fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE);
7467 }
7468 }
7469 else {
7470 /* if the app didn't request HighDPI rendering, let Windows do the upscaling */
7471 _sapp.win32.dpi.aware = false;
7472 fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE);
7473 }
7474 }
7475 else if (fn_setprocessdpiaware) {
7476 // fallback for Windows 7
7477 _sapp.win32.dpi.aware = true;
7478 fn_setprocessdpiaware();
7479 }
7480 /* get dpi scale factor for main monitor */
7481 if (fn_getdpiformonitor && _sapp.win32.dpi.aware) {
7482 POINT pt = { 1, 1 };
7483 HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
7484 UINT dpix, dpiy;
7485 HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy);
7486 _SOKOL_UNUSED(hr);
7487 SOKOL_ASSERT(SUCCEEDED(hr));
7488 /* clamp window scale to an integer factor */
7489 _sapp.win32.dpi.window_scale = (float)dpix / 96.0f;
7490 }
7491 else {
7492 _sapp.win32.dpi.window_scale = 1.0f;
7493 }
7494 if (_sapp.desc.high_dpi) {
7495 _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale;
7496 _sapp.win32.dpi.mouse_scale = 1.0f;
7497 }
7498 else {
7499 _sapp.win32.dpi.content_scale = 1.0f;
7500 _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale;
7501 }
7502 _sapp.dpi_scale = _sapp.win32.dpi.content_scale;
7503 if (user32) {
7504 FreeLibrary(user32);
7505 }
7506 if (shcore) {
7507 FreeLibrary(shcore);
7508 }
7509}
7510
7511_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) {
7512 SOKOL_ASSERT(str);
7513 SOKOL_ASSERT(_sapp.win32.hwnd);
7514 SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0));
7515
7516 wchar_t* wchar_buf = 0;
7517 const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t);
7518 HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size);
7519 if (!object) {
7520 goto error;
7521 }
7522 wchar_buf = (wchar_t*) GlobalLock(object);
7523 if (!wchar_buf) {
7524 goto error;
7525 }
7526 if (!_sapp_win32_uwp_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) {
7527 goto error;
7528 }
7529 GlobalUnlock(wchar_buf);
7530 wchar_buf = 0;
7531 if (!OpenClipboard(_sapp.win32.hwnd)) {
7532 goto error;
7533 }
7534 EmptyClipboard();
7535 SetClipboardData(CF_UNICODETEXT, object);
7536 CloseClipboard();
7537 return true;
7538
7539error:
7540 if (wchar_buf) {
7541 GlobalUnlock(object);
7542 }
7543 if (object) {
7544 GlobalFree(object);
7545 }
7546 return false;
7547}
7548
7549_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) {
7550 SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer);
7551 SOKOL_ASSERT(_sapp.win32.hwnd);
7552 if (!OpenClipboard(_sapp.win32.hwnd)) {
7553 /* silently ignore any errors and just return the current
7554 content of the local clipboard buffer
7555 */
7556 return _sapp.clipboard.buffer;
7557 }
7558 HANDLE object = GetClipboardData(CF_UNICODETEXT);
7559 if (!object) {
7560 CloseClipboard();
7561 return _sapp.clipboard.buffer;
7562 }
7563 const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object);
7564 if (!wchar_buf) {
7565 CloseClipboard();
7566 return _sapp.clipboard.buffer;
7567 }
7568 if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) {
7569 SAPP_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n");
7570 }
7571 GlobalUnlock(object);
7572 CloseClipboard();
7573 return _sapp.clipboard.buffer;
7574}
7575
7576_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) {
7577 _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide));
7578 SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide);
7579}
7580
7581_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) {
7582 BITMAPV5HEADER bi;
7583 _sapp_clear(&bi, sizeof(bi));
7584 bi.bV5Size = sizeof(bi);
7585 bi.bV5Width = desc->width;
7586 bi.bV5Height = -desc->height; // NOTE the '-' here to indicate that origin is top-left
7587 bi.bV5Planes = 1;
7588 bi.bV5BitCount = 32;
7589 bi.bV5Compression = BI_BITFIELDS;
7590 bi.bV5RedMask = 0x00FF0000;
7591 bi.bV5GreenMask = 0x0000FF00;
7592 bi.bV5BlueMask = 0x000000FF;
7593 bi.bV5AlphaMask = 0xFF000000;
7594
7595 uint8_t* target = 0;
7596 const uint8_t* source = (const uint8_t*)desc->pixels.ptr;
7597
7598 HDC dc = GetDC(NULL);
7599 HBITMAP color = CreateDIBSection(dc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&target, NULL, (DWORD)0);
7600 ReleaseDC(NULL, dc);
7601 if (0 == color) {
7602 return NULL;
7603 }
7604 SOKOL_ASSERT(target);
7605
7606 HBITMAP mask = CreateBitmap(desc->width, desc->height, 1, 1, NULL);
7607 if (0 == mask) {
7608 DeleteObject(color);
7609 return NULL;
7610 }
7611
7612 for (int i = 0; i < (desc->width*desc->height); i++) {
7613 target[0] = source[2];
7614 target[1] = source[1];
7615 target[2] = source[0];
7616 target[3] = source[3];
7617 target += 4;
7618 source += 4;
7619 }
7620
7621 ICONINFO icon_info;
7622 _sapp_clear(&icon_info, sizeof(icon_info));
7623 icon_info.fIcon = true;
7624 icon_info.xHotspot = 0;
7625 icon_info.yHotspot = 0;
7626 icon_info.hbmMask = mask;
7627 icon_info.hbmColor = color;
7628 HICON icon_handle = CreateIconIndirect(&icon_info);
7629 DeleteObject(color);
7630 DeleteObject(mask);
7631
7632 return icon_handle;
7633}
7634
7635_SOKOL_PRIVATE void _sapp_win32_set_icon(const sapp_icon_desc* icon_desc, int num_images) {
7636 SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES));
7637
7638 int big_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
7639 int sml_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7640 HICON big_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[big_img_index]);
7641 HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index]);
7642
7643 // if icon creation or lookup has failed for some reason, leave the currently set icon untouched
7644 if (0 != big_icon) {
7645 SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_BIG, (LPARAM) big_icon);
7646 if (0 != _sapp.win32.big_icon) {
7647 DestroyIcon(_sapp.win32.big_icon);
7648 }
7649 _sapp.win32.big_icon = big_icon;
7650 }
7651 if (0 != sml_icon) {
7652 SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_SMALL, (LPARAM) sml_icon);
7653 if (0 != _sapp.win32.small_icon) {
7654 DestroyIcon(_sapp.win32.small_icon);
7655 }
7656 _sapp.win32.small_icon = sml_icon;
7657 }
7658}
7659
7660/* don't laugh, but this seems to be the easiest and most robust
7661 way to check if we're running on Win10
7662
7663 From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263
7664*/
7665_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) {
7666 HMODULE h = GetModuleHandleW(L"kernel32.dll");
7667 if (NULL != h) {
7668 return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation"));
7669 }
7670 else {
7671 return false;
7672 }
7673}
7674
7675_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
7676 _sapp_init_state(desc);
7677 _sapp_win32_init_console();
7678 _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater();
7679 _sapp_win32_uwp_init_keytable();
7680 _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide));
7681 _sapp_win32_init_dpi();
7682 _sapp_win32_init_cursors();
7683 _sapp_win32_create_window();
7684 sapp_set_icon(&desc->icon);
7685 #if defined(SOKOL_D3D11)
7686 _sapp_d3d11_create_device_and_swapchain();
7687 _sapp_d3d11_create_default_render_target();
7688 #endif
7689 #if defined(SOKOL_GLCORE33)
7690 _sapp_wgl_init();
7691 _sapp_wgl_load_extensions();
7692 _sapp_wgl_create_context();
7693 #endif
7694 _sapp.valid = true;
7695
7696 bool done = false;
7697 while (!(done || _sapp.quit_ordered)) {
7698 _sapp_win32_timing_measure();
7699 MSG msg;
7700 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
7701 if (WM_QUIT == msg.message) {
7702 done = true;
7703 continue;
7704 }
7705 else {
7706 TranslateMessage(&msg);
7707 DispatchMessageW(&msg);
7708 }
7709 }
7710 _sapp_frame();
7711 #if defined(SOKOL_D3D11)
7712 _sapp_d3d11_present(false);
7713 if (IsIconic(_sapp.win32.hwnd)) {
7714 Sleep((DWORD)(16 * _sapp.swap_interval));
7715 }
7716 #endif
7717 #if defined(SOKOL_GLCORE33)
7718 _sapp_wgl_swap_buffers();
7719 #endif
7720 /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */
7721 if (_sapp_win32_update_dimensions()) {
7722 #if defined(SOKOL_D3D11)
7723 _sapp_d3d11_resize_default_render_target();
7724 #endif
7725 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED);
7726 }
7727 /* check if the window monitor has changed, need to reset timing because
7728 the new monitor might have a different refresh rate
7729 */
7730 if (_sapp_win32_update_monitor()) {
7731 _sapp_timing_reset(&_sapp.timing);
7732 }
7733 if (_sapp.quit_requested) {
7734 PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0);
7735 }
7736 }
7737 _sapp_call_cleanup();
7738
7739 #if defined(SOKOL_D3D11)
7740 _sapp_d3d11_destroy_default_render_target();
7741 _sapp_d3d11_destroy_device_and_swapchain();
7742 #else
7743 _sapp_wgl_destroy_context();
7744 _sapp_wgl_shutdown();
7745 #endif
7746 _sapp_win32_destroy_window();
7747 _sapp_win32_destroy_icons();
7748 _sapp_win32_restore_console();
7749 _sapp_discard_state();
7750}
7751
7752_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) {
7753 int argc = 0;
7754 char** argv = 0;
7755 char* args;
7756
7757 LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc);
7758 if (w_argv == NULL) {
7759 _sapp_fail("Win32: failed to parse command line");
7760 } else {
7761 size_t size = wcslen(w_command_line) * 4;
7762 argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size);
7763 SOKOL_ASSERT(argv);
7764 args = (char*) &argv[argc + 1];
7765 int n;
7766 for (int i = 0; i < argc; ++i) {
7767 n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL);
7768 if (n == 0) {
7769 _sapp_fail("Win32: failed to convert all arguments to utf8");
7770 break;
7771 }
7772 argv[i] = args;
7773 size -= (size_t)n;
7774 args += n;
7775 }
7776 LocalFree(w_argv);
7777 }
7778 *o_argc = argc;
7779 return argv;
7780}
7781
7782#if !defined(SOKOL_NO_ENTRY)
7783#if defined(SOKOL_WIN32_FORCE_MAIN)
7784int main(int argc, char* argv[]) {
7785 sapp_desc desc = sokol_main(argc, argv);
7786 _sapp_win32_run(&desc);
7787 return 0;
7788}
7789#else
7790int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) {
7791 _SOKOL_UNUSED(hInstance);
7792 _SOKOL_UNUSED(hPrevInstance);
7793 _SOKOL_UNUSED(lpCmdLine);
7794 _SOKOL_UNUSED(nCmdShow);
7795 int argc_utf8 = 0;
7796 char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8);
7797 sapp_desc desc = sokol_main(argc_utf8, argv_utf8);
7798 _sapp_win32_run(&desc);
7799 _sapp_free(argv_utf8);
7800 return 0;
7801}
7802#endif /* SOKOL_WIN32_FORCE_MAIN */
7803#endif /* SOKOL_NO_ENTRY */
7804
7805#ifdef _MSC_VER
7806 #pragma warning(pop)
7807#endif
7808
7809#endif /* _SAPP_WIN32 */
7810
7811/*== UWP ================================================================*/
7812#if defined(_SAPP_UWP)
7813
7814// Helper functions
7815_SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) {
7816 _sapp.uwp.dpi.window_scale = monitor_dpi / 96.0f;
7817 if (_sapp.desc.high_dpi) {
7818 _sapp.uwp.dpi.content_scale = _sapp.uwp.dpi.window_scale;
7819 _sapp.uwp.dpi.mouse_scale = 1.0f * _sapp.uwp.dpi.window_scale;
7820 }
7821 else {
7822 _sapp.uwp.dpi.content_scale = 1.0f;
7823 _sapp.uwp.dpi.mouse_scale = 1.0f;
7824 }
7825 _sapp.dpi_scale = _sapp.uwp.dpi.content_scale;
7826}
7827
7828_SOKOL_PRIVATE void _sapp_uwp_update_cursor(sapp_mouse_cursor cursor, bool shown) {
7829 using namespace winrt::Windows::UI::Core;
7830
7831 CoreCursor uwp_cursor(nullptr);
7832 if (shown) {
7833 switch (cursor) {
7834 case SAPP_MOUSECURSOR_ARROW: uwp_cursor = CoreCursor(CoreCursorType::Arrow, 0); break;
7835 case SAPP_MOUSECURSOR_IBEAM: uwp_cursor = CoreCursor(CoreCursorType::IBeam, 0); break;
7836 case SAPP_MOUSECURSOR_CROSSHAIR: uwp_cursor = CoreCursor(CoreCursorType::Cross, 0); break;
7837 case SAPP_MOUSECURSOR_POINTING_HAND: uwp_cursor = CoreCursor(CoreCursorType::Hand, 0); break;
7838 case SAPP_MOUSECURSOR_RESIZE_EW: uwp_cursor = CoreCursor(CoreCursorType::SizeWestEast, 0); break;
7839 case SAPP_MOUSECURSOR_RESIZE_NS: uwp_cursor = CoreCursor(CoreCursorType::SizeNorthSouth, 0); break;
7840 case SAPP_MOUSECURSOR_RESIZE_NWSE: uwp_cursor = CoreCursor(CoreCursorType::SizeNorthwestSoutheast, 0); break;
7841 case SAPP_MOUSECURSOR_RESIZE_NESW: uwp_cursor = CoreCursor(CoreCursorType::SizeNortheastSouthwest, 0); break;
7842 case SAPP_MOUSECURSOR_RESIZE_ALL: uwp_cursor = CoreCursor(CoreCursorType::SizeAll, 0); break;
7843 case SAPP_MOUSECURSOR_NOT_ALLOWED: uwp_cursor = CoreCursor(CoreCursorType::UniversalNo, 0); break;
7844 default: uwp_cursor = CoreCursor(CoreCursorType::Arrow, 0); break;
7845 }
7846 }
7847 CoreWindow::GetForCurrentThread().PointerCursor(uwp_cursor);
7848}
7849
7850_SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) {
7851 using namespace winrt::Windows::System;
7852 using namespace winrt::Windows::UI::Core;
7853
7854 uint32_t mods = 0;
7855 if ((sender_window.GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) {
7856 mods |= SAPP_MODIFIER_SHIFT;
7857 }
7858 if ((sender_window.GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) {
7859 mods |= SAPP_MODIFIER_CTRL;
7860 }
7861 if ((sender_window.GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) {
7862 mods |= SAPP_MODIFIER_ALT;
7863 }
7864 if (((sender_window.GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) ||
7865 ((sender_window.GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down))
7866 {
7867 mods |= SAPP_MODIFIER_SUPER;
7868 }
7869 if (0 != (_sapp.uwp.mouse_buttons & (1<<SAPP_MOUSEBUTTON_LEFT))) {
7870 mods |= SAPP_MODIFIER_LMB;
7871 }
7872 if (0 != (_sapp.uwp.mouse_buttons & (1<<SAPP_MOUSEBUTTON_MIDDLE))) {
7873 mods |= SAPP_MODIFIER_MMB;
7874 }
7875 if (0 != (_sapp.uwp.mouse_buttons & (1<<SAPP_MOUSEBUTTON_RIGHT))) {
7876 mods |= SAPP_MODIFIER_RMB;
7877 }
7878 return mods;
7879}
7880
7881_SOKOL_PRIVATE void _sapp_uwp_mouse_event(sapp_event_type type, sapp_mousebutton btn, winrt::Windows::UI::Core::CoreWindow const& sender_window) {
7882 if (_sapp_events_enabled()) {
7883 _sapp_init_event(type);
7884 _sapp.event.modifiers = _sapp_uwp_mods(sender_window);
7885 _sapp.event.mouse_button = btn;
7886 _sapp_call_event(&_sapp.event);
7887 }
7888}
7889
7890_SOKOL_PRIVATE void _sapp_uwp_scroll_event(float delta, bool horizontal, winrt::Windows::UI::Core::CoreWindow const& sender_window) {
7891 if (_sapp_events_enabled()) {
7892 _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL);
7893 _sapp.event.modifiers = _sapp_uwp_mods(sender_window);
7894 _sapp.event.scroll_x = horizontal ? (-delta / 30.0f) : 0.0f;
7895 _sapp.event.scroll_y = horizontal ? 0.0f : (delta / 30.0f);
7896 _sapp_call_event(&_sapp.event);
7897 }
7898}
7899
7900_SOKOL_PRIVATE void _sapp_uwp_extract_mouse_button_events(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
7901
7902 // we need to figure out ourselves what mouse buttons have been pressed and released,
7903 // because UWP doesn't properly send down/up mouse button events when multiple buttons
7904 // are pressed down, so we also need to check the mouse button state in other mouse events
7905 // to track what buttons have been pressed down and released
7906 //
7907 auto properties = args.CurrentPoint().Properties();
7908 const uint8_t lmb_bit = (1 << SAPP_MOUSEBUTTON_LEFT);
7909 const uint8_t rmb_bit = (1 << SAPP_MOUSEBUTTON_RIGHT);
7910 const uint8_t mmb_bit = (1 << SAPP_MOUSEBUTTON_MIDDLE);
7911 uint8_t new_btns = 0;
7912 if (properties.IsLeftButtonPressed()) {
7913 new_btns |= lmb_bit;
7914 }
7915 if (properties.IsRightButtonPressed()) {
7916 new_btns |= rmb_bit;
7917 }
7918 if (properties.IsMiddleButtonPressed()) {
7919 new_btns |= mmb_bit;
7920 }
7921 const uint8_t old_btns = _sapp.uwp.mouse_buttons;
7922 const uint8_t chg_btns = new_btns ^ old_btns;
7923
7924 _sapp.uwp.mouse_buttons = new_btns;
7925
7926 sapp_event_type type = SAPP_EVENTTYPE_INVALID;
7927 sapp_mousebutton btn = SAPP_MOUSEBUTTON_INVALID;
7928 if (chg_btns & lmb_bit) {
7929 btn = SAPP_MOUSEBUTTON_LEFT;
7930 type = (new_btns & lmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP;
7931 }
7932 if (chg_btns & rmb_bit) {
7933 btn = SAPP_MOUSEBUTTON_RIGHT;
7934 type = (new_btns & rmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP;
7935 }
7936 if (chg_btns & mmb_bit) {
7937 btn = SAPP_MOUSEBUTTON_MIDDLE;
7938 type = (new_btns & mmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP;
7939 }
7940 if (type != SAPP_EVENTTYPE_INVALID) {
7941 _sapp_uwp_mouse_event(type, btn, sender);
7942 }
7943}
7944
7945_SOKOL_PRIVATE void _sapp_uwp_key_event(sapp_event_type type, winrt::Windows::UI::Core::CoreWindow const& sender_window, winrt::Windows::UI::Core::KeyEventArgs const& args) {
7946 auto key_status = args.KeyStatus();
7947 uint32_t ext_scan_code = key_status.ScanCode | (key_status.IsExtendedKey ? 0x100 : 0);
7948 if (_sapp_events_enabled() && (ext_scan_code < SAPP_MAX_KEYCODES)) {
7949 _sapp_init_event(type);
7950 _sapp.event.modifiers = _sapp_uwp_mods(sender_window);
7951 _sapp.event.key_code = _sapp.keycodes[ext_scan_code];
7952 _sapp.event.key_repeat = type == SAPP_EVENTTYPE_KEY_UP ? false : key_status.WasKeyDown;
7953 _sapp_call_event(&_sapp.event);
7954 /* check if a CLIPBOARD_PASTED event must be sent too */
7955 if (_sapp.clipboard.enabled &&
7956 (type == SAPP_EVENTTYPE_KEY_DOWN) &&
7957 (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) &&
7958 (_sapp.event.key_code == SAPP_KEYCODE_V))
7959 {
7960 _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
7961 _sapp_call_event(&_sapp.event);
7962 }
7963 }
7964}
7965
7966_SOKOL_PRIVATE void _sapp_uwp_char_event(uint32_t c, bool repeat, winrt::Windows::UI::Core::CoreWindow const& sender_window) {
7967 if (_sapp_events_enabled() && (c >= 32)) {
7968 _sapp_init_event(SAPP_EVENTTYPE_CHAR);
7969 _sapp.event.modifiers = _sapp_uwp_mods(sender_window);
7970 _sapp.event.char_code = c;
7971 _sapp.event.key_repeat = repeat;
7972 _sapp_call_event(&_sapp.event);
7973 }
7974}
7975
7976_SOKOL_PRIVATE void _sapp_uwp_toggle_fullscreen(void) {
7977 auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView();
7978 _sapp.fullscreen = appView.IsFullScreenMode();
7979 if (!_sapp.fullscreen) {
7980 appView.TryEnterFullScreenMode();
7981 }
7982 else {
7983 appView.ExitFullScreenMode();
7984 }
7985 _sapp.fullscreen = appView.IsFullScreenMode();
7986}
7987
7988namespace {/* Empty namespace to ensure internal linkage (same as _SOKOL_PRIVATE) */
7989
7990// Controls all the DirectX device resources.
7991class DeviceResources {
7992public:
7993 // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created.
7994 interface IDeviceNotify {
7995 virtual void OnDeviceLost() = 0;
7996 virtual void OnDeviceRestored() = 0;
7997 };
7998
7999 DeviceResources();
8000 ~DeviceResources();
8001 void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window);
8002 void SetLogicalSize(winrt::Windows::Foundation::Size logicalSize);
8003 void SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation);
8004 void SetDpi(float dpi);
8005 void ValidateDevice();
8006 void HandleDeviceLost();
8007 void RegisterDeviceNotify(IDeviceNotify* deviceNotify);
8008 void Trim();
8009 void Present();
8010
8011private:
8012
8013 // Swapchain Rotation Matrices (Z-rotation)
8014 static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation0 = {
8015 1.0f, 0.0f, 0.0f, 0.0f,
8016 0.0f, 1.0f, 0.0f, 0.0f,
8017 0.0f, 0.0f, 1.0f, 0.0f,
8018 0.0f, 0.0f, 0.0f, 1.0f
8019 };
8020 static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation90 = {
8021 0.0f, 1.0f, 0.0f, 0.0f,
8022 -1.0f, 0.0f, 0.0f, 0.0f,
8023 0.0f, 0.0f, 1.0f, 0.0f,
8024 0.0f, 0.0f, 0.0f, 1.0f
8025 };
8026 static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation180 = {
8027 -1.0f, 0.0f, 0.0f, 0.0f,
8028 0.0f, -1.0f, 0.0f, 0.0f,
8029 0.0f, 0.0f, 1.0f, 0.0f,
8030 0.0f, 0.0f, 0.0f, 1.0f
8031 };
8032 static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation270 = {
8033 0.0f, -1.0f, 0.0f, 0.0f,
8034 1.0f, 0.0f, 0.0f, 0.0f,
8035 0.0f, 0.0f, 1.0f, 0.0f,
8036 0.0f, 0.0f, 0.0f, 1.0f
8037 };
8038
8039 void CreateDeviceResources();
8040 void CreateWindowSizeDependentResources();
8041 void UpdateRenderTargetSize();
8042 DXGI_MODE_ROTATION ComputeDisplayRotation();
8043 bool SdkLayersAvailable();
8044
8045 // Direct3D objects.
8046 winrt::com_ptr<ID3D11Device3> m_d3dDevice;
8047 winrt::com_ptr<ID3D11DeviceContext3> m_d3dContext;
8048 winrt::com_ptr<IDXGISwapChain3> m_swapChain;
8049
8050 // Direct3D rendering objects. Required for 3D.
8051 winrt::com_ptr<ID3D11Texture2D1> m_d3dRenderTarget;
8052 winrt::com_ptr<ID3D11RenderTargetView1> m_d3dRenderTargetView;
8053 winrt::com_ptr<ID3D11Texture2D1> m_d3dMSAARenderTarget;
8054 winrt::com_ptr<ID3D11RenderTargetView1> m_d3dMSAARenderTargetView;
8055 winrt::com_ptr<ID3D11Texture2D1> m_d3dDepthStencil;
8056 winrt::com_ptr<ID3D11DepthStencilView> m_d3dDepthStencilView;
8057 D3D11_VIEWPORT m_screenViewport = { };
8058
8059 // Cached reference to the Window.
8060 winrt::agile_ref< winrt::Windows::UI::Core::CoreWindow> m_window;
8061
8062 // Cached device properties.
8063 D3D_FEATURE_LEVEL m_d3dFeatureLevel = D3D_FEATURE_LEVEL_9_1;
8064 winrt::Windows::Foundation::Size m_d3dRenderTargetSize = { };
8065 winrt::Windows::Foundation::Size m_outputSize = { };
8066 winrt::Windows::Foundation::Size m_logicalSize = { };
8067 winrt::Windows::Graphics::Display::DisplayOrientations m_nativeOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None;
8068 winrt::Windows::Graphics::Display::DisplayOrientations m_currentOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None;
8069 float m_dpi = -1.0f;
8070
8071 // Transforms used for display orientation.
8072 DirectX::XMFLOAT4X4 m_orientationTransform3D;
8073
8074 // The IDeviceNotify can be held directly as it owns the DeviceResources.
8075 IDeviceNotify* m_deviceNotify = nullptr;
8076};
8077
8078// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events.
8079struct App : winrt::implements<App, winrt::Windows::ApplicationModel::Core::IFrameworkViewSource, winrt::Windows::ApplicationModel::Core::IFrameworkView> {
8080public:
8081 // IFrameworkViewSource Methods
8082 winrt::Windows::ApplicationModel::Core::IFrameworkView CreateView() { return *this; }
8083
8084 // IFrameworkView Methods.
8085 virtual void Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView);
8086 virtual void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window);
8087 virtual void Load(winrt::hstring const& entryPoint);
8088 virtual void Run();
8089 virtual void Uninitialize();
8090
8091protected:
8092 // Application lifecycle event handlers
8093 void OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args);
8094 void OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args);
8095 void OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args);
8096
8097 // Window event handlers
8098 void OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args);
8099 void OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args);
8100
8101 // Navigation event handlers
8102 void OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args);
8103
8104 // Input event handlers
8105 void OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args);
8106 void OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args);
8107 void OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args);
8108
8109 // Pointer event handlers
8110 void OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8111 void OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8112 void OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8113 void OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8114 void OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8115 void OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args);
8116
8117 // DisplayInformation event handlers.
8118 void OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args);
8119 void OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args);
8120 void OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args);
8121
8122private:
8123 std::unique_ptr<DeviceResources> m_deviceResources;
8124 bool m_windowVisible = true;
8125};
8126
8127DeviceResources::DeviceResources() {
8128 CreateDeviceResources();
8129}
8130
8131DeviceResources::~DeviceResources() {
8132 // Cleanup Sokol Context
8133 _sapp.d3d11.device = nullptr;
8134 _sapp.d3d11.device_context = nullptr;
8135}
8136
8137void DeviceResources::CreateDeviceResources() {
8138 // This flag adds support for surfaces with a different color channel ordering
8139 // than the API default. It is required for compatibility with Direct2D.
8140 UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
8141
8142 #if defined(_DEBUG)
8143 if (SdkLayersAvailable()) {
8144 // If the project is in a debug build, enable debugging via SDK Layers with this flag.
8145 creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
8146 }
8147 #endif
8148
8149 // This array defines the set of DirectX hardware feature levels this app will support.
8150 // Note the ordering should be preserved.
8151 // Don't forget to declare your application's minimum required feature level in its
8152 // description. All applications are assumed to support 9.1 unless otherwise stated.
8153 D3D_FEATURE_LEVEL featureLevels[] = {
8154 D3D_FEATURE_LEVEL_12_1,
8155 D3D_FEATURE_LEVEL_12_0,
8156 D3D_FEATURE_LEVEL_11_1,
8157 D3D_FEATURE_LEVEL_11_0,
8158 D3D_FEATURE_LEVEL_10_1,
8159 D3D_FEATURE_LEVEL_10_0,
8160 D3D_FEATURE_LEVEL_9_3,
8161 D3D_FEATURE_LEVEL_9_2,
8162 D3D_FEATURE_LEVEL_9_1
8163 };
8164
8165 // Create the Direct3D 11 API device object and a corresponding context.
8166 winrt::com_ptr<ID3D11Device> device;
8167 winrt::com_ptr<ID3D11DeviceContext> context;
8168
8169 HRESULT hr = D3D11CreateDevice(
8170 nullptr, // Specify nullptr to use the default adapter.
8171 D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
8172 0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
8173 creationFlags, // Set debug and Direct2D compatibility flags.
8174 featureLevels, // List of feature levels this app can support.
8175 ARRAYSIZE(featureLevels), // Size of the list above.
8176 D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps.
8177 device.put(), // Returns the Direct3D device created.
8178 &m_d3dFeatureLevel, // Returns feature level of device created.
8179 context.put() // Returns the device immediate context.
8180 );
8181
8182 if (FAILED(hr)) {
8183 // If the initialization fails, fall back to the WARP device.
8184 // For more information on WARP, see:
8185 // https://go.microsoft.com/fwlink/?LinkId=286690
8186 winrt::check_hresult(
8187 D3D11CreateDevice(
8188 nullptr,
8189 D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
8190 0,
8191 creationFlags,
8192 featureLevels,
8193 ARRAYSIZE(featureLevels),
8194 D3D11_SDK_VERSION,
8195 device.put(),
8196 &m_d3dFeatureLevel,
8197 context.put()
8198 )
8199 );
8200 }
8201
8202 // Store pointers to the Direct3D 11.3 API device and immediate context.
8203 m_d3dDevice = device.as<ID3D11Device3>();
8204 m_d3dContext = context.as<ID3D11DeviceContext3>();
8205
8206 // Setup Sokol Context
8207 _sapp.d3d11.device = m_d3dDevice.get();
8208 _sapp.d3d11.device_context = m_d3dContext.get();
8209}
8210
8211void DeviceResources::CreateWindowSizeDependentResources() {
8212 // Cleanup Sokol Context (these are non-owning raw pointers)
8213 _sapp.d3d11.rt = nullptr;
8214 _sapp.d3d11.rtv = nullptr;
8215 _sapp.d3d11.msaa_rt = nullptr;
8216 _sapp.d3d11.msaa_rtv = nullptr;
8217 _sapp.d3d11.ds = nullptr;
8218 _sapp.d3d11.dsv = nullptr;
8219
8220 // Clear the previous window size specific context.
8221 ID3D11RenderTargetView* nullViews[] = { nullptr };
8222 m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
8223 // these are smart pointers, setting to nullptr will delete the objects
8224 m_d3dRenderTarget = nullptr;
8225 m_d3dRenderTargetView = nullptr;
8226 m_d3dMSAARenderTarget = nullptr;
8227 m_d3dMSAARenderTargetView = nullptr;
8228 m_d3dDepthStencilView = nullptr;
8229 m_d3dDepthStencil = nullptr;
8230 m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr);
8231
8232 UpdateRenderTargetSize();
8233
8234 // The width and height of the swap chain must be based on the window's
8235 // natively-oriented width and height. If the window is not in the native
8236 // orientation, the dimensions must be reversed.
8237 DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();
8238
8239 bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
8240 m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
8241 m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;
8242
8243 if (m_swapChain != nullptr) {
8244 // If the swap chain already exists, resize it.
8245 HRESULT hr = m_swapChain->ResizeBuffers(
8246 2, // Double-buffered swap chain.
8247 lround(m_d3dRenderTargetSize.Width),
8248 lround(m_d3dRenderTargetSize.Height),
8249 DXGI_FORMAT_B8G8R8A8_UNORM,
8250 0
8251 );
8252
8253 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
8254 // If the device was removed for any reason, a new device and swap chain will need to be created.
8255 HandleDeviceLost();
8256
8257 // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method
8258 // and correctly set up the new device.
8259 return;
8260 }
8261 else {
8262 winrt::check_hresult(hr);
8263 }
8264 }
8265 else {
8266 // Otherwise, create a new one using the same adapter as the existing Direct3D device.
8267 DXGI_SCALING scaling = (_sapp.uwp.dpi.content_scale == _sapp.uwp.dpi.window_scale) ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH;
8268 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
8269
8270 swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window.
8271 swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height);
8272 swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
8273 swapChainDesc.Stereo = false;
8274 swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
8275 swapChainDesc.SampleDesc.Quality = 0;
8276 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
8277 swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
8278 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Microsoft Store apps must use this SwapEffect.
8279 swapChainDesc.Flags = 0;
8280 swapChainDesc.Scaling = scaling;
8281 swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
8282
8283 // This sequence obtains the DXGI factory that was used to create the Direct3D device above.
8284 winrt::com_ptr<IDXGIDevice3> dxgiDevice = m_d3dDevice.as<IDXGIDevice3>();
8285 winrt::com_ptr<IDXGIAdapter> dxgiAdapter;
8286 winrt::check_hresult(dxgiDevice->GetAdapter(dxgiAdapter.put()));
8287 winrt::com_ptr<IDXGIFactory4> dxgiFactory;
8288 winrt::check_hresult(dxgiAdapter->GetParent(__uuidof(IDXGIFactory4), dxgiFactory.put_void()));
8289 winrt::com_ptr<IDXGISwapChain1> swapChain;
8290 winrt::check_hresult(dxgiFactory->CreateSwapChainForCoreWindow(m_d3dDevice.get(), m_window.get().as<::IUnknown>().get(), &swapChainDesc, nullptr, swapChain.put()));
8291 m_swapChain = swapChain.as<IDXGISwapChain3>();
8292
8293 // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
8294 // ensures that the application will only render after each VSync, minimizing power consumption.
8295 winrt::check_hresult(dxgiDevice->SetMaximumFrameLatency(1));
8296
8297 // Setup Sokol Context
8298 winrt::check_hresult(swapChain->GetDesc(&_sapp.d3d11.swap_chain_desc));
8299 _sapp.d3d11.swap_chain = m_swapChain.as<IDXGISwapChain3>().detach();
8300 }
8301
8302 // Set the proper orientation for the swap chain, and generate 2D and
8303 // 3D matrix transformations for rendering to the rotated swap chain.
8304 // Note the rotation angle for the 2D and 3D transforms are different.
8305 // This is due to the difference in coordinate spaces. Additionally,
8306 // the 3D matrix is specified explicitly to avoid rounding errors.
8307 switch (displayRotation) {
8308 case DXGI_MODE_ROTATION_IDENTITY:
8309 m_orientationTransform3D = m_rotation0;
8310 break;
8311
8312 case DXGI_MODE_ROTATION_ROTATE90:
8313 m_orientationTransform3D = m_rotation270;
8314 break;
8315
8316 case DXGI_MODE_ROTATION_ROTATE180:
8317 m_orientationTransform3D = m_rotation180;
8318 break;
8319
8320 case DXGI_MODE_ROTATION_ROTATE270:
8321 m_orientationTransform3D = m_rotation90;
8322 break;
8323 }
8324 winrt::check_hresult(m_swapChain->SetRotation(displayRotation));
8325
8326 // Create a render target view of the swap chain back buffer.
8327 winrt::check_hresult(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_d3dRenderTarget)));
8328 winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dRenderTarget.get(), nullptr, m_d3dRenderTargetView.put()));
8329
8330 // Create MSAA texture and view if needed
8331 if (_sapp.sample_count > 1) {
8332 CD3D11_TEXTURE2D_DESC1 msaaTexDesc(
8333 DXGI_FORMAT_B8G8R8A8_UNORM,
8334 lround(m_d3dRenderTargetSize.Width),
8335 lround(m_d3dRenderTargetSize.Height),
8336 1, // arraySize
8337 1, // mipLevels
8338 D3D11_BIND_RENDER_TARGET,
8339 D3D11_USAGE_DEFAULT,
8340 0, // cpuAccessFlags
8341 _sapp.sample_count,
8342 _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0
8343 );
8344 winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&msaaTexDesc, nullptr, m_d3dMSAARenderTarget.put()));
8345 winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dMSAARenderTarget.get(), nullptr, m_d3dMSAARenderTargetView.put()));
8346 }
8347
8348 // Create a depth stencil view for use with 3D rendering if needed.
8349 CD3D11_TEXTURE2D_DESC1 depthStencilDesc(
8350 DXGI_FORMAT_D24_UNORM_S8_UINT,
8351 lround(m_d3dRenderTargetSize.Width),
8352 lround(m_d3dRenderTargetSize.Height),
8353 1, // This depth stencil view has only one texture.
8354 1, // Use a single mipmap level.
8355 D3D11_BIND_DEPTH_STENCIL,
8356 D3D11_USAGE_DEFAULT,
8357 0, // cpuAccessFlag
8358 _sapp.sample_count,
8359 _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0
8360 );
8361 winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&depthStencilDesc, nullptr, m_d3dDepthStencil.put()));
8362
8363 CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
8364 winrt::check_hresult(m_d3dDevice->CreateDepthStencilView(m_d3dDepthStencil.get(), nullptr, m_d3dDepthStencilView.put()));
8365
8366 // Set sokol window and framebuffer sizes
8367 _sapp.window_width = (int) m_logicalSize.Width;
8368 _sapp.window_height = (int) m_logicalSize.Height;
8369 _sapp.framebuffer_width = lround(m_d3dRenderTargetSize.Width);
8370 _sapp.framebuffer_height = lround(m_d3dRenderTargetSize.Height);
8371
8372 // Setup Sokol Context
8373 _sapp.d3d11.rt = m_d3dRenderTarget.as<ID3D11Texture2D>().get();
8374 _sapp.d3d11.rtv = m_d3dRenderTargetView.as<ID3D11RenderTargetView>().get();
8375 _sapp.d3d11.ds = m_d3dDepthStencil.as<ID3D11Texture2D>().get();
8376 _sapp.d3d11.dsv = m_d3dDepthStencilView.get();
8377 if (_sapp.sample_count > 1) {
8378 _sapp.d3d11.msaa_rt = m_d3dMSAARenderTarget.as<ID3D11Texture2D>().get();
8379 _sapp.d3d11.msaa_rtv = m_d3dMSAARenderTargetView.as<ID3D11RenderTargetView>().get();
8380 }
8381
8382 // Sokol app is now valid
8383 _sapp.valid = true;
8384}
8385
8386// Determine the dimensions of the render target and whether it will be scaled down.
8387void DeviceResources::UpdateRenderTargetSize() {
8388 // Calculate the necessary render target size in pixels.
8389 m_outputSize.Width = m_logicalSize.Width * _sapp.uwp.dpi.content_scale;
8390 m_outputSize.Height = m_logicalSize.Height * _sapp.uwp.dpi.content_scale;
8391
8392 // Prevent zero size DirectX content from being created.
8393 m_outputSize.Width = std::max(m_outputSize.Width, 1.0f);
8394 m_outputSize.Height = std::max(m_outputSize.Height, 1.0f);
8395}
8396
8397// This method is called when the CoreWindow is created (or re-created).
8398void DeviceResources::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) {
8399 auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView();
8400 m_window = window;
8401 m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height);
8402 m_nativeOrientation = currentDisplayInformation.NativeOrientation();
8403 m_currentOrientation = currentDisplayInformation.CurrentOrientation();
8404 m_dpi = currentDisplayInformation.LogicalDpi();
8405 _sapp_uwp_configure_dpi(m_dpi);
8406 CreateWindowSizeDependentResources();
8407}
8408
8409// This method is called in the event handler for the SizeChanged event.
8410void DeviceResources::SetLogicalSize(winrt::Windows::Foundation::Size logicalSize) {
8411 if (m_logicalSize != logicalSize) {
8412 m_logicalSize = logicalSize;
8413 CreateWindowSizeDependentResources();
8414 }
8415}
8416
8417// This method is called in the event handler for the DpiChanged event.
8418void DeviceResources::SetDpi(float dpi) {
8419 if (dpi != m_dpi) {
8420 m_dpi = dpi;
8421 _sapp_uwp_configure_dpi(m_dpi);
8422 // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated.
8423 auto window = m_window.get();
8424 m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height);
8425 CreateWindowSizeDependentResources();
8426 }
8427}
8428
8429// This method is called in the event handler for the OrientationChanged event.
8430void DeviceResources::SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation) {
8431 if (m_currentOrientation != currentOrientation) {
8432 m_currentOrientation = currentOrientation;
8433 CreateWindowSizeDependentResources();
8434 }
8435}
8436
8437// This method is called in the event handler for the DisplayContentsInvalidated event.
8438void DeviceResources::ValidateDevice() {
8439 // The D3D Device is no longer valid if the default adapter changed since the device
8440 // was created or if the device has been removed.
8441
8442 // First, get the information for the default adapter from when the device was created.
8443 winrt::com_ptr<IDXGIDevice3> dxgiDevice = m_d3dDevice.as< IDXGIDevice3>();
8444 winrt::com_ptr<IDXGIAdapter> deviceAdapter;
8445 winrt::check_hresult(dxgiDevice->GetAdapter(deviceAdapter.put()));
8446 winrt::com_ptr<IDXGIFactory4> deviceFactory;
8447 winrt::check_hresult(deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory)));
8448 winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter;
8449 winrt::check_hresult(deviceFactory->EnumAdapters1(0, previousDefaultAdapter.put()));
8450 DXGI_ADAPTER_DESC1 previousDesc;
8451 winrt::check_hresult(previousDefaultAdapter->GetDesc1(&previousDesc));
8452
8453 // Next, get the information for the current default adapter.
8454 winrt::com_ptr<IDXGIFactory4> currentFactory;
8455 winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory)));
8456 winrt::com_ptr<IDXGIAdapter1> currentDefaultAdapter;
8457 winrt::check_hresult(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put()));
8458 DXGI_ADAPTER_DESC1 currentDesc;
8459 winrt::check_hresult(currentDefaultAdapter->GetDesc1(¤tDesc));
8460
8461 // If the adapter LUIDs don't match, or if the device reports that it has been removed,
8462 // a new D3D device must be created.
8463 if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart ||
8464 previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart ||
8465 FAILED(m_d3dDevice->GetDeviceRemovedReason()))
8466 {
8467 // Release references to resources related to the old device.
8468 dxgiDevice = nullptr;
8469 deviceAdapter = nullptr;
8470 deviceFactory = nullptr;
8471 previousDefaultAdapter = nullptr;
8472
8473 // Create a new device and swap chain.
8474 HandleDeviceLost();
8475 }
8476}
8477
8478// Recreate all device resources and set them back to the current state.
8479void DeviceResources::HandleDeviceLost() {
8480 m_swapChain = nullptr;
8481 if (m_deviceNotify != nullptr) {
8482 m_deviceNotify->OnDeviceLost();
8483 }
8484 CreateDeviceResources();
8485 CreateWindowSizeDependentResources();
8486 if (m_deviceNotify != nullptr) {
8487 m_deviceNotify->OnDeviceRestored();
8488 }
8489}
8490
8491// Register our DeviceNotify to be informed on device lost and creation.
8492void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) {
8493 m_deviceNotify = deviceNotify;
8494}
8495
8496// Call this method when the app suspends. It provides a hint to the driver that the app
8497// is entering an idle state and that temporary buffers can be reclaimed for use by other apps.
8498void DeviceResources::Trim() {
8499 m_d3dDevice.as<IDXGIDevice3>()->Trim();
8500}
8501
8502// Present the contents of the swap chain to the screen.
8503void DeviceResources::Present() {
8504
8505 // MSAA resolve if needed
8506 if (_sapp.sample_count > 1) {
8507 m_d3dContext->ResolveSubresource(m_d3dRenderTarget.get(), 0, m_d3dMSAARenderTarget.get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM);
8508 m_d3dContext->DiscardView1(m_d3dMSAARenderTargetView.get(), nullptr, 0);
8509 }
8510
8511 // The first argument instructs DXGI to block until VSync, putting the application
8512 // to sleep until the next VSync. This ensures we don't waste any cycles rendering
8513 // frames that will never be displayed to the screen.
8514 DXGI_PRESENT_PARAMETERS parameters = { 0 };
8515 HRESULT hr = m_swapChain->Present1(1, 0, ¶meters);
8516
8517 // Discard the contents of the render target.
8518 // This is a valid operation only when the existing contents will be entirely
8519 // overwritten. If dirty or scroll rects are used, this call should be removed.
8520 m_d3dContext->DiscardView1(m_d3dRenderTargetView.get(), nullptr, 0);
8521
8522 // Discard the contents of the depth stencil.
8523 m_d3dContext->DiscardView1(m_d3dDepthStencilView.get(), nullptr, 0);
8524
8525 // If the device was removed either by a disconnection or a driver upgrade, we
8526 // must recreate all device resources.
8527 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
8528 HandleDeviceLost();
8529 }
8530 else {
8531 winrt::check_hresult(hr);
8532 }
8533}
8534
8535// This method determines the rotation between the display device's native orientation and the
8536// current display orientation.
8537DXGI_MODE_ROTATION DeviceResources::ComputeDisplayRotation() {
8538 DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
8539
8540 // Note: NativeOrientation can only be Landscape or Portrait even though
8541 // the DisplayOrientations enum has other values.
8542 switch (m_nativeOrientation) {
8543 case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape:
8544 switch (m_currentOrientation) {
8545 case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape:
8546 rotation = DXGI_MODE_ROTATION_IDENTITY;
8547 break;
8548
8549 case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait:
8550 rotation = DXGI_MODE_ROTATION_ROTATE270;
8551 break;
8552
8553 case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
8554 rotation = DXGI_MODE_ROTATION_ROTATE180;
8555 break;
8556
8557 case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped:
8558 rotation = DXGI_MODE_ROTATION_ROTATE90;
8559 break;
8560 }
8561 break;
8562
8563 case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait:
8564 switch (m_currentOrientation) {
8565 case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape:
8566 rotation = DXGI_MODE_ROTATION_ROTATE90;
8567 break;
8568
8569 case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait:
8570 rotation = DXGI_MODE_ROTATION_IDENTITY;
8571 break;
8572
8573 case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
8574 rotation = DXGI_MODE_ROTATION_ROTATE270;
8575 break;
8576
8577 case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped:
8578 rotation = DXGI_MODE_ROTATION_ROTATE180;
8579 break;
8580 }
8581 break;
8582 }
8583 return rotation;
8584}
8585
8586// Check for SDK Layer support.
8587bool DeviceResources::SdkLayersAvailable() {
8588 #if defined(_DEBUG)
8589 HRESULT hr = D3D11CreateDevice(
8590 nullptr,
8591 D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device.
8592 0,
8593 D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers.
8594 nullptr, // Any feature level will do.
8595 0,
8596 D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps.
8597 nullptr, // No need to keep the D3D device reference.
8598 nullptr, // No need to know the feature level.
8599 nullptr // No need to keep the D3D device context reference.
8600 );
8601 return SUCCEEDED(hr);
8602 #else
8603 return false;
8604 #endif
8605}
8606
8607// The first method called when the IFrameworkView is being created.
8608void App::Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) {
8609 // Register event handlers for app lifecycle. This example includes Activated, so that we
8610 // can make the CoreWindow active and start rendering on the window.
8611 applicationView.Activated({ this, &App::OnActivated });
8612
8613 winrt::Windows::ApplicationModel::Core::CoreApplication::Suspending({ this, &App::OnSuspending });
8614 winrt::Windows::ApplicationModel::Core::CoreApplication::Resuming({ this, &App::OnResuming });
8615
8616 // At this point we have access to the device.
8617 // We can create the device-dependent resources.
8618 m_deviceResources = std::make_unique<DeviceResources>();
8619}
8620
8621// Called when the CoreWindow object is created (or re-created).
8622void App::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) {
8623 window.SizeChanged({ this, &App::OnWindowSizeChanged });
8624 window.VisibilityChanged({ this, &App::OnVisibilityChanged });
8625
8626 window.KeyDown({ this, &App::OnKeyDown });
8627 window.KeyUp({ this, &App::OnKeyUp });
8628 window.CharacterReceived({ this, &App::OnCharacterReceived });
8629
8630 window.PointerEntered({ this, &App::OnPointerEntered });
8631 window.PointerExited({ this, &App::OnPointerExited });
8632 window.PointerPressed({ this, &App::OnPointerPressed });
8633 window.PointerReleased({ this, &App::OnPointerReleased });
8634 window.PointerMoved({ this, &App::OnPointerMoved });
8635 window.PointerWheelChanged({ this, &App::OnPointerWheelChanged });
8636
8637 auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView();
8638
8639 currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged });
8640 currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged });
8641 winrt::Windows::Graphics::Display::DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated });
8642
8643 winrt::Windows::UI::Core::SystemNavigationManager::GetForCurrentView().BackRequested({ this, &App::OnBackRequested });
8644
8645 m_deviceResources->SetWindow(window);
8646}
8647
8648// Initializes scene resources, or loads a previously saved app state.
8649void App::Load(winrt::hstring const& entryPoint) {
8650 _SOKOL_UNUSED(entryPoint);
8651}
8652
8653// This method is called after the window becomes active.
8654void App::Run() {
8655 // NOTE: UWP will simply terminate an application, it's not possible to detect when an application is being closed
8656 while (true) {
8657 if (m_windowVisible) {
8658 _sapp_timing_measure(&_sapp.timing);
8659 winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent);
8660 _sapp_frame();
8661 m_deviceResources->Present();
8662 }
8663 else {
8664 winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessOneAndAllPending);
8665 }
8666 }
8667}
8668
8669// Required for IFrameworkView.
8670// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView
8671// class is torn down while the app is in the foreground.
8672void App::Uninitialize() {
8673 // empty
8674}
8675
8676// Application lifecycle event handlers.
8677void App::OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) {
8678 _SOKOL_UNUSED(args);
8679 _SOKOL_UNUSED(applicationView);
8680 auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView();
8681 const float window_width = (float)_sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH);
8682 const float window_height = (float)_sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT);
8683 auto targetSize = winrt::Windows::Foundation::Size(window_width, window_height);
8684 winrt::Windows::UI::ViewManagement::ApplicationView::PreferredLaunchViewSize(targetSize);
8685 winrt::Windows::UI::ViewManagement::ApplicationView::PreferredLaunchWindowingMode(winrt::Windows::UI::ViewManagement::ApplicationViewWindowingMode::PreferredLaunchViewSize);
8686 appView.SetPreferredMinSize(targetSize);
8687 appView.TryResizeView(targetSize);
8688
8689 // Disabling this since it can only append the title to the app name (Title - Appname).
8690 // There's no way of just setting a string to be the window title.
8691 //appView.Title(_sapp.window_title_wide);
8692
8693 // Run() won't start until the CoreWindow is activated.
8694 winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Activate();
8695 if (_sapp.desc.fullscreen) {
8696 appView.TryEnterFullScreenMode();
8697 }
8698 _sapp.fullscreen = appView.IsFullScreenMode();
8699}
8700
8701void App::OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args) {
8702 _SOKOL_UNUSED(sender);
8703 _SOKOL_UNUSED(args);
8704 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_SUSPENDED);
8705}
8706
8707void App::OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args) {
8708 _SOKOL_UNUSED(args);
8709 _SOKOL_UNUSED(sender);
8710 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESUMED);
8711}
8712
8713void App::OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args) {
8714 _SOKOL_UNUSED(args);
8715 m_deviceResources->SetLogicalSize(winrt::Windows::Foundation::Size(sender.Bounds().Width, sender.Bounds().Height));
8716 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED);
8717}
8718
8719void App::OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) {
8720 _SOKOL_UNUSED(sender);
8721 m_windowVisible = args.Visible();
8722 _sapp_win32_uwp_app_event(m_windowVisible ? SAPP_EVENTTYPE_RESTORED : SAPP_EVENTTYPE_ICONIFIED);
8723}
8724
8725void App::OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args) {
8726 _SOKOL_UNUSED(sender);
8727 args.Handled(true);
8728}
8729
8730void App::OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) {
8731 auto status = args.KeyStatus();
8732 _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_DOWN, sender, args);
8733}
8734
8735void App::OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) {
8736 auto status = args.KeyStatus();
8737 _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_UP, sender, args);
8738}
8739
8740void App::OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) {
8741 _sapp_uwp_char_event(args.KeyCode(), args.KeyStatus().WasKeyDown, sender);
8742}
8743
8744void App::OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8745 _SOKOL_UNUSED(args);
8746 _sapp.uwp.mouse_tracked = true;
8747 _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender);
8748}
8749
8750void App::OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8751 _SOKOL_UNUSED(args);
8752 _sapp.uwp.mouse_tracked = false;
8753 _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, sender);
8754}
8755
8756void App::OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8757 _sapp_uwp_extract_mouse_button_events(sender, args);
8758}
8759
8760// NOTE: for some reason this event handler is never called??
8761void App::OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8762 _sapp_uwp_extract_mouse_button_events(sender, args);
8763}
8764
8765void App::OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8766 auto position = args.CurrentPoint().Position();
8767 const float new_x = (float)(int)(position.X * _sapp.uwp.dpi.mouse_scale + 0.5f);
8768 const float new_y = (float)(int)(position.Y * _sapp.uwp.dpi.mouse_scale + 0.5f);
8769 // don't update dx/dy in the very first event
8770 if (_sapp.mouse.pos_valid) {
8771 _sapp.mouse.dx = new_x - _sapp.mouse.x;
8772 _sapp.mouse.dy = new_y - _sapp.mouse.y;
8773 }
8774 _sapp.mouse.x = new_x;
8775 _sapp.mouse.y = new_y;
8776 _sapp.mouse.pos_valid = true;
8777 if (!_sapp.uwp.mouse_tracked) {
8778 _sapp.uwp.mouse_tracked = true;
8779 _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender);
8780 }
8781 _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, sender);
8782
8783 // HACK for detecting multiple mouse button presses
8784 _sapp_uwp_extract_mouse_button_events(sender, args);
8785}
8786
8787void App::OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) {
8788 auto properties = args.CurrentPoint().Properties();
8789 _sapp_uwp_scroll_event((float)properties.MouseWheelDelta(), properties.IsHorizontalMouseWheel(), sender);
8790}
8791
8792void App::OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) {
8793 // NOTE: UNTESTED
8794 _SOKOL_UNUSED(args);
8795 m_deviceResources->SetDpi(sender.LogicalDpi());
8796 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED);
8797}
8798
8799void App::OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) {
8800 // NOTE: UNTESTED
8801 _SOKOL_UNUSED(args);
8802 m_deviceResources->SetCurrentOrientation(sender.CurrentOrientation());
8803 _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED);
8804}
8805
8806void App::OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) {
8807 // NOTE: UNTESTED
8808 _SOKOL_UNUSED(args);
8809 _SOKOL_UNUSED(sender);
8810 m_deviceResources->ValidateDevice();
8811}
8812
8813} /* End empty namespace */
8814
8815_SOKOL_PRIVATE void _sapp_uwp_run(const sapp_desc* desc) {
8816 _sapp_init_state(desc);
8817 _sapp_win32_uwp_init_keytable();
8818 _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide));
8819 winrt::Windows::ApplicationModel::Core::CoreApplication::Run(winrt::make<App>());
8820}
8821
8822#if !defined(SOKOL_NO_ENTRY)
8823#if defined(UNICODE)
8824int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
8825#else
8826int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) {
8827#endif
8828 _SOKOL_UNUSED(hInstance);
8829 _SOKOL_UNUSED(hPrevInstance);
8830 _SOKOL_UNUSED(lpCmdLine);
8831 _SOKOL_UNUSED(nCmdShow);
8832 sapp_desc desc = sokol_main(0, nullptr);
8833 _sapp_uwp_run(&desc);
8834 return 0;
8835}
8836#endif /* SOKOL_NO_ENTRY */
8837#endif /* _SAPP_UWP */
8838
8839/*== Android ================================================================*/
8840#if defined(_SAPP_ANDROID)
8841
8842/* android loop thread */
8843_SOKOL_PRIVATE bool _sapp_android_init_egl(void) {
8844 SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY);
8845 SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT);
8846
8847 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
8848 if (display == EGL_NO_DISPLAY) {
8849 return false;
8850 }
8851 if (eglInitialize(display, NULL, NULL) == EGL_FALSE) {
8852 return false;
8853 }
8854 _sapp.gles2_fallback = _sapp.desc.gl_force_gles2;
8855
8856 EGLint alpha_size = _sapp.desc.alpha ? 8 : 0;
8857 const EGLint cfg_attributes[] = {
8858 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
8859 #if defined(SOKOL_GLES3)
8860 EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2?EGL_OPENGL_ES2_BIT:EGL_OPENGL_ES3_BIT,
8861 #else
8862 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
8863 #endif
8864 EGL_RED_SIZE, 8,
8865 EGL_GREEN_SIZE, 8,
8866 EGL_BLUE_SIZE, 8,
8867 EGL_ALPHA_SIZE, alpha_size,
8868 EGL_DEPTH_SIZE, 16,
8869 EGL_STENCIL_SIZE, 0,
8870 EGL_NONE,
8871 };
8872 EGLConfig available_cfgs[32];
8873 EGLint cfg_count;
8874 eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count);
8875 SOKOL_ASSERT(cfg_count > 0);
8876 SOKOL_ASSERT(cfg_count <= 32);
8877
8878 /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */
8879 EGLConfig config;
8880 bool exact_cfg_found = false;
8881 for (int i = 0; i < cfg_count; ++i) {
8882 EGLConfig c = available_cfgs[i];
8883 EGLint r, g, b, a, d;
8884 if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE &&
8885 eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE &&
8886 eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE &&
8887 eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE &&
8888 eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE &&
8889 r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) {
8890 exact_cfg_found = true;
8891 config = c;
8892 break;
8893 }
8894 }
8895 if (!exact_cfg_found) {
8896 config = available_cfgs[0];
8897 }
8898
8899 EGLint ctx_attributes[] = {
8900 #if defined(SOKOL_GLES3)
8901 EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3,
8902 #else
8903 EGL_CONTEXT_CLIENT_VERSION, 2,
8904 #endif
8905 EGL_NONE,
8906 };
8907 EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes);
8908 if (context == EGL_NO_CONTEXT) {
8909 return false;
8910 }
8911
8912 _sapp.android.config = config;
8913 _sapp.android.display = display;
8914 _sapp.android.context = context;
8915 return true;
8916}
8917
8918_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) {
8919 if (_sapp.android.display != EGL_NO_DISPLAY) {
8920 eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
8921 if (_sapp.android.surface != EGL_NO_SURFACE) {
8922 SAPP_LOG("Destroying egl surface");
8923 eglDestroySurface(_sapp.android.display, _sapp.android.surface);
8924 _sapp.android.surface = EGL_NO_SURFACE;
8925 }
8926 if (_sapp.android.context != EGL_NO_CONTEXT) {
8927 SAPP_LOG("Destroying egl context");
8928 eglDestroyContext(_sapp.android.display, _sapp.android.context);
8929 _sapp.android.context = EGL_NO_CONTEXT;
8930 }
8931 SAPP_LOG("Terminating egl display");
8932 eglTerminate(_sapp.android.display);
8933 _sapp.android.display = EGL_NO_DISPLAY;
8934 }
8935}
8936
8937_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) {
8938 SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY);
8939 SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT);
8940 SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE);
8941 SOKOL_ASSERT(window);
8942
8943 /* TODO: set window flags */
8944 /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */
8945
8946 /* create egl surface and make it current */
8947 EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL);
8948 if (surface == EGL_NO_SURFACE) {
8949 return false;
8950 }
8951 if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) {
8952 return false;
8953 }
8954 _sapp.android.surface = surface;
8955 return true;
8956}
8957
8958_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) {
8959 if (_sapp.android.display == EGL_NO_DISPLAY) {
8960 return;
8961 }
8962 eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
8963 if (_sapp.android.surface != EGL_NO_SURFACE) {
8964 eglDestroySurface(_sapp.android.display, _sapp.android.surface);
8965 _sapp.android.surface = EGL_NO_SURFACE;
8966 }
8967}
8968
8969_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) {
8970 if (_sapp_events_enabled()) {
8971 _sapp_init_event(type);
8972 SAPP_LOG("event_cb()");
8973 _sapp_call_event(&_sapp.event);
8974 }
8975}
8976
8977_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) {
8978 SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY);
8979 SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT);
8980 SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE);
8981 SOKOL_ASSERT(window);
8982
8983 const int32_t win_w = ANativeWindow_getWidth(window);
8984 const int32_t win_h = ANativeWindow_getHeight(window);
8985 SOKOL_ASSERT(win_w >= 0 && win_h >= 0);
8986 const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height);
8987 _sapp.window_width = win_w;
8988 _sapp.window_height = win_h;
8989 if (win_changed || force_update) {
8990 if (!_sapp.desc.high_dpi) {
8991 const int32_t buf_w = win_w / 2;
8992 const int32_t buf_h = win_h / 2;
8993 EGLint format;
8994 EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format);
8995 SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result);
8996 /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions
8997 as the ANativeWindow size results in weird display artefacts, that's
8998 why it's only called when the buffer geometry is different from
8999 the window size
9000 */
9001 int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format);
9002 SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result);
9003 }
9004 }
9005
9006 /* query surface size */
9007 EGLint fb_w, fb_h;
9008 EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w);
9009 EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h);
9010 SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w);
9011 SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h);
9012 const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height);
9013 _sapp.framebuffer_width = fb_w;
9014 _sapp.framebuffer_height = fb_h;
9015 _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width;
9016 if (win_changed || fb_changed || force_update) {
9017 if (!_sapp.first_frame) {
9018 SAPP_LOG("SAPP_EVENTTYPE_RESIZED");
9019 _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED);
9020 }
9021 }
9022}
9023
9024_SOKOL_PRIVATE void _sapp_android_cleanup(void) {
9025 SAPP_LOG("Cleaning up");
9026 if (_sapp.android.surface != EGL_NO_SURFACE) {
9027 /* egl context is bound, cleanup gracefully */
9028 if (_sapp.init_called && !_sapp.cleanup_called) {
9029 SAPP_LOG("cleanup_cb()");
9030 _sapp_call_cleanup();
9031 }
9032 }
9033 /* always try to cleanup by destroying egl context */
9034 _sapp_android_cleanup_egl();
9035}
9036
9037_SOKOL_PRIVATE void _sapp_android_shutdown(void) {
9038 /* try to cleanup while we still have a surface and can call cleanup_cb() */
9039 _sapp_android_cleanup();
9040 /* request exit */
9041 ANativeActivity_finish(_sapp.android.activity);
9042}
9043
9044_SOKOL_PRIVATE void _sapp_android_frame(void) {
9045 SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY);
9046 SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT);
9047 SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE);
9048 _sapp_timing_measure(&_sapp.timing);
9049 _sapp_android_update_dimensions(_sapp.android.current.window, false);
9050 _sapp_frame();
9051 eglSwapBuffers(_sapp.android.display, _sapp.android.surface);
9052}
9053
9054_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) {
9055 if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) {
9056 return false;
9057 }
9058 if (!_sapp_events_enabled()) {
9059 return false;
9060 }
9061 int32_t action_idx = AMotionEvent_getAction(e);
9062 int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK;
9063 sapp_event_type type = SAPP_EVENTTYPE_INVALID;
9064 switch (action) {
9065 case AMOTION_EVENT_ACTION_DOWN:
9066 SAPP_LOG("Touch: down");
9067 case AMOTION_EVENT_ACTION_POINTER_DOWN:
9068 SAPP_LOG("Touch: ptr down");
9069 type = SAPP_EVENTTYPE_TOUCHES_BEGAN;
9070 break;
9071 case AMOTION_EVENT_ACTION_MOVE:
9072 type = SAPP_EVENTTYPE_TOUCHES_MOVED;
9073 break;
9074 case AMOTION_EVENT_ACTION_UP:
9075 SAPP_LOG("Touch: up");
9076 case AMOTION_EVENT_ACTION_POINTER_UP:
9077 SAPP_LOG("Touch: ptr up");
9078 type = SAPP_EVENTTYPE_TOUCHES_ENDED;
9079 break;
9080 case AMOTION_EVENT_ACTION_CANCEL:
9081 SAPP_LOG("Touch: cancel");
9082 type = SAPP_EVENTTYPE_TOUCHES_CANCELLED;
9083 break;
9084 default:
9085 break;
9086 }
9087 if (type == SAPP_EVENTTYPE_INVALID) {
9088 return false;
9089 }
9090 int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
9091 _sapp_init_event(type);
9092 _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e);
9093 if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) {
9094 _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS;
9095 }
9096 for (int32_t i = 0; i < _sapp.event.num_touches; i++) {
9097 sapp_touchpoint* dst = &_sapp.event.touches[i];
9098 dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i);
9099 dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width;
9100 dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height;
9101 dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i);
9102 if (action == AMOTION_EVENT_ACTION_POINTER_DOWN ||
9103 action == AMOTION_EVENT_ACTION_POINTER_UP) {
9104 dst->changed = (i == idx);
9105 } else {
9106 dst->changed = true;
9107 }
9108 }
9109 _sapp_call_event(&_sapp.event);
9110 return true;
9111}
9112
9113_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) {
9114 if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) {
9115 return false;
9116 }
9117 if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) {
9118 /* FIXME: this should be hooked into a "really quit?" mechanism
9119 so the app can ask the user for confirmation, this is currently
9120 generally missing in sokol_app.h
9121 */
9122 _sapp_android_shutdown();
9123 return true;
9124 }
9125 return false;
9126}
9127
9128_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) {
9129 _SOKOL_UNUSED(fd);
9130 _SOKOL_UNUSED(data);
9131 if ((events & ALOOPER_EVENT_INPUT) == 0) {
9132 SAPP_LOG("_sapp_android_input_cb() encountered unsupported event");
9133 return 1;
9134 }
9135 SOKOL_ASSERT(_sapp.android.current.input);
9136 AInputEvent* event = NULL;
9137 while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) {
9138 if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) {
9139 continue;
9140 }
9141 int32_t handled = 0;
9142 if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) {
9143 handled = 1;
9144 }
9145 AInputQueue_finishEvent(_sapp.android.current.input, event, handled);
9146 }
9147 return 1;
9148}
9149
9150_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
9151 _SOKOL_UNUSED(data);
9152 if ((events & ALOOPER_EVENT_INPUT) == 0) {
9153 SAPP_LOG("_sapp_android_main_cb() encountered unsupported event");
9154 return 1;
9155 }
9156
9157 _sapp_android_msg_t msg;
9158 if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) {
9159 SAPP_LOG("Could not write to read_from_main_fd");
9160 return 1;
9161 }
9162
9163 pthread_mutex_lock(&_sapp.android.pt.mutex);
9164 switch (msg) {
9165 case _SOKOL_ANDROID_MSG_CREATE:
9166 {
9167 SAPP_LOG("MSG_CREATE");
9168 SOKOL_ASSERT(!_sapp.valid);
9169 bool result = _sapp_android_init_egl();
9170 SOKOL_ASSERT(result); _SOKOL_UNUSED(result);
9171 _sapp.valid = true;
9172 _sapp.android.has_created = true;
9173 }
9174 break;
9175 case _SOKOL_ANDROID_MSG_RESUME:
9176 SAPP_LOG("MSG_RESUME");
9177 _sapp.android.has_resumed = true;
9178 _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED);
9179 break;
9180 case _SOKOL_ANDROID_MSG_PAUSE:
9181 SAPP_LOG("MSG_PAUSE");
9182 _sapp.android.has_resumed = false;
9183 _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED);
9184 break;
9185 case _SOKOL_ANDROID_MSG_FOCUS:
9186 SAPP_LOG("MSG_FOCUS");
9187 _sapp.android.has_focus = true;
9188 break;
9189 case _SOKOL_ANDROID_MSG_NO_FOCUS:
9190 SAPP_LOG("MSG_NO_FOCUS");
9191 _sapp.android.has_focus = false;
9192 break;
9193 case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW:
9194 SAPP_LOG("MSG_SET_NATIVE_WINDOW");
9195 if (_sapp.android.current.window != _sapp.android.pending.window) {
9196 if (_sapp.android.current.window != NULL) {
9197 _sapp_android_cleanup_egl_surface();
9198 }
9199 if (_sapp.android.pending.window != NULL) {
9200 SAPP_LOG("Creating egl surface ...");
9201 if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) {
9202 SAPP_LOG("... ok!");
9203 _sapp_android_update_dimensions(_sapp.android.pending.window, true);
9204 } else {
9205 SAPP_LOG("... failed!");
9206 _sapp_android_shutdown();
9207 }
9208 }
9209 }
9210 _sapp.android.current.window = _sapp.android.pending.window;
9211 break;
9212 case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE:
9213 SAPP_LOG("MSG_SET_INPUT_QUEUE");
9214 if (_sapp.android.current.input != _sapp.android.pending.input) {
9215 if (_sapp.android.current.input != NULL) {
9216 AInputQueue_detachLooper(_sapp.android.current.input);
9217 }
9218 if (_sapp.android.pending.input != NULL) {
9219 AInputQueue_attachLooper(
9220 _sapp.android.pending.input,
9221 _sapp.android.looper,
9222 ALOOPER_POLL_CALLBACK,
9223 _sapp_android_input_cb,
9224 NULL); /* data */
9225 }
9226 }
9227 _sapp.android.current.input = _sapp.android.pending.input;
9228 break;
9229 case _SOKOL_ANDROID_MSG_DESTROY:
9230 SAPP_LOG("MSG_DESTROY");
9231 _sapp_android_cleanup();
9232 _sapp.valid = false;
9233 _sapp.android.is_thread_stopping = true;
9234 break;
9235 default:
9236 SAPP_LOG("Unknown msg type received");
9237 break;
9238 }
9239 pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */
9240 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9241 return 1;
9242}
9243
9244_SOKOL_PRIVATE bool _sapp_android_should_update(void) {
9245 bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus;
9246 bool has_surface = _sapp.android.surface != EGL_NO_SURFACE;
9247 return is_in_front && has_surface;
9248}
9249
9250_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) {
9251 SOKOL_ASSERT(_sapp.valid);
9252 /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */
9253 if (shown) {
9254 SAPP_LOG("Showing keyboard");
9255 ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED);
9256 } else {
9257 SAPP_LOG("Hiding keyboard");
9258 ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS);
9259 }
9260}
9261
9262_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) {
9263 _SOKOL_UNUSED(arg);
9264 SAPP_LOG("Loop thread started");
9265
9266 _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/);
9267 ALooper_addFd(_sapp.android.looper,
9268 _sapp.android.pt.read_from_main_fd,
9269 ALOOPER_POLL_CALLBACK,
9270 ALOOPER_EVENT_INPUT,
9271 _sapp_android_main_cb,
9272 NULL); /* data */
9273
9274 /* signal start to main thread */
9275 pthread_mutex_lock(&_sapp.android.pt.mutex);
9276 _sapp.android.is_thread_started = true;
9277 pthread_cond_broadcast(&_sapp.android.pt.cond);
9278 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9279
9280 /* main loop */
9281 while (!_sapp.android.is_thread_stopping) {
9282 /* sokol frame */
9283 if (_sapp_android_should_update()) {
9284 _sapp_android_frame();
9285 }
9286
9287 /* process all events (or stop early if app is requested to quit) */
9288 bool process_events = true;
9289 while (process_events && !_sapp.android.is_thread_stopping) {
9290 bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update();
9291 process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK;
9292 }
9293 }
9294
9295 /* cleanup thread */
9296 if (_sapp.android.current.input != NULL) {
9297 AInputQueue_detachLooper(_sapp.android.current.input);
9298 }
9299
9300 /* the following causes heap corruption on exit, why??
9301 ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd);
9302 ALooper_release(_sapp.android.looper);*/
9303
9304 /* signal "destroyed" */
9305 pthread_mutex_lock(&_sapp.android.pt.mutex);
9306 _sapp.android.is_thread_stopped = true;
9307 pthread_cond_broadcast(&_sapp.android.pt.cond);
9308 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9309 SAPP_LOG("Loop thread done");
9310 return NULL;
9311}
9312
9313/* android main/ui thread */
9314_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) {
9315 if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) {
9316 SAPP_LOG("Could not write to write_from_main_fd");
9317 }
9318}
9319
9320_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) {
9321 _SOKOL_UNUSED(activity);
9322 SAPP_LOG("NativeActivity onStart()");
9323}
9324
9325_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) {
9326 _SOKOL_UNUSED(activity);
9327 SAPP_LOG("NativeActivity onResume()");
9328 _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME);
9329}
9330
9331_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) {
9332 _SOKOL_UNUSED(activity);
9333 SAPP_LOG("NativeActivity onSaveInstanceState()");
9334 *out_size = 0;
9335 return NULL;
9336}
9337
9338_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) {
9339 _SOKOL_UNUSED(activity);
9340 SAPP_LOG("NativeActivity onWindowFocusChanged()");
9341 if (has_focus) {
9342 _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS);
9343 } else {
9344 _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS);
9345 }
9346}
9347
9348_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) {
9349 _SOKOL_UNUSED(activity);
9350 SAPP_LOG("NativeActivity onPause()");
9351 _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE);
9352}
9353
9354_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) {
9355 _SOKOL_UNUSED(activity);
9356 SAPP_LOG("NativeActivity onStop()");
9357}
9358
9359_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) {
9360 pthread_mutex_lock(&_sapp.android.pt.mutex);
9361 _sapp.android.pending.window = window;
9362 _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW);
9363 while (_sapp.android.current.window != window) {
9364 pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex);
9365 }
9366 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9367}
9368
9369_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) {
9370 _SOKOL_UNUSED(activity);
9371 SAPP_LOG("NativeActivity onNativeWindowCreated()");
9372 _sapp_android_msg_set_native_window(window);
9373}
9374
9375_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) {
9376 _SOKOL_UNUSED(activity);
9377 _SOKOL_UNUSED(window);
9378 SAPP_LOG("NativeActivity onNativeWindowDestroyed()");
9379 _sapp_android_msg_set_native_window(NULL);
9380}
9381
9382_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) {
9383 pthread_mutex_lock(&_sapp.android.pt.mutex);
9384 _sapp.android.pending.input = input;
9385 _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE);
9386 while (_sapp.android.current.input != input) {
9387 pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex);
9388 }
9389 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9390}
9391
9392_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) {
9393 _SOKOL_UNUSED(activity);
9394 SAPP_LOG("NativeActivity onInputQueueCreated()");
9395 _sapp_android_msg_set_input_queue(queue);
9396}
9397
9398_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) {
9399 _SOKOL_UNUSED(activity);
9400 _SOKOL_UNUSED(queue);
9401 SAPP_LOG("NativeActivity onInputQueueDestroyed()");
9402 _sapp_android_msg_set_input_queue(NULL);
9403}
9404
9405_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) {
9406 _SOKOL_UNUSED(activity);
9407 SAPP_LOG("NativeActivity onConfigurationChanged()");
9408 /* see android:configChanges in manifest */
9409}
9410
9411_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) {
9412 _SOKOL_UNUSED(activity);
9413 SAPP_LOG("NativeActivity onLowMemory()");
9414}
9415
9416_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) {
9417 /*
9418 * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH)
9419 * on my device (Moto X 2nd gen) when the app is removed from the task view
9420 * (TaskStackView: onTaskViewDismissed).
9421 *
9422 * However, if ANativeActivity_finish() is explicitly called from for example
9423 * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity?
9424 */
9425 _SOKOL_UNUSED(activity);
9426 SAPP_LOG("NativeActivity onDestroy()");
9427
9428 /* send destroy msg */
9429 pthread_mutex_lock(&_sapp.android.pt.mutex);
9430 _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY);
9431 while (!_sapp.android.is_thread_stopped) {
9432 pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex);
9433 }
9434 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9435
9436 /* clean up main thread */
9437 pthread_cond_destroy(&_sapp.android.pt.cond);
9438 pthread_mutex_destroy(&_sapp.android.pt.mutex);
9439
9440 close(_sapp.android.pt.read_from_main_fd);
9441 close(_sapp.android.pt.write_from_main_fd);
9442
9443 SAPP_LOG("NativeActivity done");
9444
9445 /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */
9446 exit(0);
9447}
9448
9449JNIEXPORT
9450void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) {
9451 _SOKOL_UNUSED(saved_state);
9452 _SOKOL_UNUSED(saved_state_size);
9453 SAPP_LOG("NativeActivity onCreate()");
9454
9455 // the NativeActity pointer needs to be available inside sokol_main()
9456 // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state()
9457 // will clear the global _sapp_t struct, so we need to initialize the native
9458 // activity pointer twice, once before sokol_main() and once after _sapp_init_state()
9459 _sapp_clear(&_sapp, sizeof(_sapp));
9460 _sapp.android.activity = activity;
9461 sapp_desc desc = sokol_main(0, NULL);
9462 _sapp_init_state(&desc);
9463 _sapp.android.activity = activity;
9464
9465 int pipe_fd[2];
9466 if (pipe(pipe_fd) != 0) {
9467 SAPP_LOG("Could not create thread pipe");
9468 return;
9469 }
9470 _sapp.android.pt.read_from_main_fd = pipe_fd[0];
9471 _sapp.android.pt.write_from_main_fd = pipe_fd[1];
9472
9473 pthread_mutex_init(&_sapp.android.pt.mutex, NULL);
9474 pthread_cond_init(&_sapp.android.pt.cond, NULL);
9475
9476 pthread_attr_t attr;
9477 pthread_attr_init(&attr);
9478 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
9479 pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0);
9480 pthread_attr_destroy(&attr);
9481
9482 /* wait until main loop has started */
9483 pthread_mutex_lock(&_sapp.android.pt.mutex);
9484 while (!_sapp.android.is_thread_started) {
9485 pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex);
9486 }
9487 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9488
9489 /* send create msg */
9490 pthread_mutex_lock(&_sapp.android.pt.mutex);
9491 _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE);
9492 while (!_sapp.android.has_created) {
9493 pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex);
9494 }
9495 pthread_mutex_unlock(&_sapp.android.pt.mutex);
9496
9497 /* register for callbacks */
9498 activity->callbacks->onStart = _sapp_android_on_start;
9499 activity->callbacks->onResume = _sapp_android_on_resume;
9500 activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state;
9501 activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed;
9502 activity->callbacks->onPause = _sapp_android_on_pause;
9503 activity->callbacks->onStop = _sapp_android_on_stop;
9504 activity->callbacks->onDestroy = _sapp_android_on_destroy;
9505 activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created;
9506 /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */
9507 /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */
9508 activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed;
9509 activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created;
9510 activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed;
9511 /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */
9512 activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed;
9513 activity->callbacks->onLowMemory = _sapp_android_on_low_memory;
9514
9515 SAPP_LOG("NativeActivity successfully created");
9516
9517 /* NOT A BUG: do NOT call sapp_discard_state() */
9518}
9519
9520#endif /* _SAPP_ANDROID */
9521
9522/*== LINUX ==================================================================*/
9523#if defined(_SAPP_LINUX)
9524
9525/* see GLFW's xkb_unicode.c */
9526static const struct _sapp_x11_codepair {
9527 uint16_t keysym;
9528 uint16_t ucs;
9529} _sapp_x11_keysymtab[] = {
9530 { 0x01a1, 0x0104 },
9531 { 0x01a2, 0x02d8 },
9532 { 0x01a3, 0x0141 },
9533 { 0x01a5, 0x013d },
9534 { 0x01a6, 0x015a },
9535 { 0x01a9, 0x0160 },
9536 { 0x01aa, 0x015e },
9537 { 0x01ab, 0x0164 },
9538 { 0x01ac, 0x0179 },
9539 { 0x01ae, 0x017d },
9540 { 0x01af, 0x017b },
9541 { 0x01b1, 0x0105 },
9542 { 0x01b2, 0x02db },
9543 { 0x01b3, 0x0142 },
9544 { 0x01b5, 0x013e },
9545 { 0x01b6, 0x015b },
9546 { 0x01b7, 0x02c7 },
9547 { 0x01b9, 0x0161 },
9548 { 0x01ba, 0x015f },
9549 { 0x01bb, 0x0165 },
9550 { 0x01bc, 0x017a },
9551 { 0x01bd, 0x02dd },
9552 { 0x01be, 0x017e },
9553 { 0x01bf, 0x017c },
9554 { 0x01c0, 0x0154 },
9555 { 0x01c3, 0x0102 },
9556 { 0x01c5, 0x0139 },
9557 { 0x01c6, 0x0106 },
9558 { 0x01c8, 0x010c },
9559 { 0x01ca, 0x0118 },
9560 { 0x01cc, 0x011a },
9561 { 0x01cf, 0x010e },
9562 { 0x01d0, 0x0110 },
9563 { 0x01d1, 0x0143 },
9564 { 0x01d2, 0x0147 },
9565 { 0x01d5, 0x0150 },
9566 { 0x01d8, 0x0158 },
9567 { 0x01d9, 0x016e },
9568 { 0x01db, 0x0170 },
9569 { 0x01de, 0x0162 },
9570 { 0x01e0, 0x0155 },
9571 { 0x01e3, 0x0103 },
9572 { 0x01e5, 0x013a },
9573 { 0x01e6, 0x0107 },
9574 { 0x01e8, 0x010d },
9575 { 0x01ea, 0x0119 },
9576 { 0x01ec, 0x011b },
9577 { 0x01ef, 0x010f },
9578 { 0x01f0, 0x0111 },
9579 { 0x01f1, 0x0144 },
9580 { 0x01f2, 0x0148 },
9581 { 0x01f5, 0x0151 },
9582 { 0x01f8, 0x0159 },
9583 { 0x01f9, 0x016f },
9584 { 0x01fb, 0x0171 },
9585 { 0x01fe, 0x0163 },
9586 { 0x01ff, 0x02d9 },
9587 { 0x02a1, 0x0126 },
9588 { 0x02a6, 0x0124 },
9589 { 0x02a9, 0x0130 },
9590 { 0x02ab, 0x011e },
9591 { 0x02ac, 0x0134 },
9592 { 0x02b1, 0x0127 },
9593 { 0x02b6, 0x0125 },
9594 { 0x02b9, 0x0131 },
9595 { 0x02bb, 0x011f },
9596 { 0x02bc, 0x0135 },
9597 { 0x02c5, 0x010a },
9598 { 0x02c6, 0x0108 },
9599 { 0x02d5, 0x0120 },
9600 { 0x02d8, 0x011c },
9601 { 0x02dd, 0x016c },
9602 { 0x02de, 0x015c },
9603 { 0x02e5, 0x010b },
9604 { 0x02e6, 0x0109 },
9605 { 0x02f5, 0x0121 },
9606 { 0x02f8, 0x011d },
9607 { 0x02fd, 0x016d },
9608 { 0x02fe, 0x015d },
9609 { 0x03a2, 0x0138 },
9610 { 0x03a3, 0x0156 },
9611 { 0x03a5, 0x0128 },
9612 { 0x03a6, 0x013b },
9613 { 0x03aa, 0x0112 },
9614 { 0x03ab, 0x0122 },
9615 { 0x03ac, 0x0166 },
9616 { 0x03b3, 0x0157 },
9617 { 0x03b5, 0x0129 },
9618 { 0x03b6, 0x013c },
9619 { 0x03ba, 0x0113 },
9620 { 0x03bb, 0x0123 },
9621 { 0x03bc, 0x0167 },
9622 { 0x03bd, 0x014a },
9623 { 0x03bf, 0x014b },
9624 { 0x03c0, 0x0100 },
9625 { 0x03c7, 0x012e },
9626 { 0x03cc, 0x0116 },
9627 { 0x03cf, 0x012a },
9628 { 0x03d1, 0x0145 },
9629 { 0x03d2, 0x014c },
9630 { 0x03d3, 0x0136 },
9631 { 0x03d9, 0x0172 },
9632 { 0x03dd, 0x0168 },
9633 { 0x03de, 0x016a },
9634 { 0x03e0, 0x0101 },
9635 { 0x03e7, 0x012f },
9636 { 0x03ec, 0x0117 },
9637 { 0x03ef, 0x012b },
9638 { 0x03f1, 0x0146 },
9639 { 0x03f2, 0x014d },
9640 { 0x03f3, 0x0137 },
9641 { 0x03f9, 0x0173 },
9642 { 0x03fd, 0x0169 },
9643 { 0x03fe, 0x016b },
9644 { 0x047e, 0x203e },
9645 { 0x04a1, 0x3002 },
9646 { 0x04a2, 0x300c },
9647 { 0x04a3, 0x300d },
9648 { 0x04a4, 0x3001 },
9649 { 0x04a5, 0x30fb },
9650 { 0x04a6, 0x30f2 },
9651 { 0x04a7, 0x30a1 },
9652 { 0x04a8, 0x30a3 },
9653 { 0x04a9, 0x30a5 },
9654 { 0x04aa, 0x30a7 },
9655 { 0x04ab, 0x30a9 },
9656 { 0x04ac, 0x30e3 },
9657 { 0x04ad, 0x30e5 },
9658 { 0x04ae, 0x30e7 },
9659 { 0x04af, 0x30c3 },
9660 { 0x04b0, 0x30fc },
9661 { 0x04b1, 0x30a2 },
9662 { 0x04b2, 0x30a4 },
9663 { 0x04b3, 0x30a6 },
9664 { 0x04b4, 0x30a8 },
9665 { 0x04b5, 0x30aa },
9666 { 0x04b6, 0x30ab },
9667 { 0x04b7, 0x30ad },
9668 { 0x04b8, 0x30af },
9669 { 0x04b9, 0x30b1 },
9670 { 0x04ba, 0x30b3 },
9671 { 0x04bb, 0x30b5 },
9672 { 0x04bc, 0x30b7 },
9673 { 0x04bd, 0x30b9 },
9674 { 0x04be, 0x30bb },
9675 { 0x04bf, 0x30bd },
9676 { 0x04c0, 0x30bf },
9677 { 0x04c1, 0x30c1 },
9678 { 0x04c2, 0x30c4 },
9679 { 0x04c3, 0x30c6 },
9680 { 0x04c4, 0x30c8 },
9681 { 0x04c5, 0x30ca },
9682 { 0x04c6, 0x30cb },
9683 { 0x04c7, 0x30cc },
9684 { 0x04c8, 0x30cd },
9685 { 0x04c9, 0x30ce },
9686 { 0x04ca, 0x30cf },
9687 { 0x04cb, 0x30d2 },
9688 { 0x04cc, 0x30d5 },
9689 { 0x04cd, 0x30d8 },
9690 { 0x04ce, 0x30db },
9691 { 0x04cf, 0x30de },
9692 { 0x04d0, 0x30df },
9693 { 0x04d1, 0x30e0 },
9694 { 0x04d2, 0x30e1 },
9695 { 0x04d3, 0x30e2 },
9696 { 0x04d4, 0x30e4 },
9697 { 0x04d5, 0x30e6 },
9698 { 0x04d6, 0x30e8 },
9699 { 0x04d7, 0x30e9 },
9700 { 0x04d8, 0x30ea },
9701 { 0x04d9, 0x30eb },
9702 { 0x04da, 0x30ec },
9703 { 0x04db, 0x30ed },
9704 { 0x04dc, 0x30ef },
9705 { 0x04dd, 0x30f3 },
9706 { 0x04de, 0x309b },
9707 { 0x04df, 0x309c },
9708 { 0x05ac, 0x060c },
9709 { 0x05bb, 0x061b },
9710 { 0x05bf, 0x061f },
9711 { 0x05c1, 0x0621 },
9712 { 0x05c2, 0x0622 },
9713 { 0x05c3, 0x0623 },
9714 { 0x05c4, 0x0624 },
9715 { 0x05c5, 0x0625 },
9716 { 0x05c6, 0x0626 },
9717 { 0x05c7, 0x0627 },
9718 { 0x05c8, 0x0628 },
9719 { 0x05c9, 0x0629 },
9720 { 0x05ca, 0x062a },
9721 { 0x05cb, 0x062b },
9722 { 0x05cc, 0x062c },
9723 { 0x05cd, 0x062d },
9724 { 0x05ce, 0x062e },
9725 { 0x05cf, 0x062f },
9726 { 0x05d0, 0x0630 },
9727 { 0x05d1, 0x0631 },
9728 { 0x05d2, 0x0632 },
9729 { 0x05d3, 0x0633 },
9730 { 0x05d4, 0x0634 },
9731 { 0x05d5, 0x0635 },
9732 { 0x05d6, 0x0636 },
9733 { 0x05d7, 0x0637 },
9734 { 0x05d8, 0x0638 },
9735 { 0x05d9, 0x0639 },
9736 { 0x05da, 0x063a },
9737 { 0x05e0, 0x0640 },
9738 { 0x05e1, 0x0641 },
9739 { 0x05e2, 0x0642 },
9740 { 0x05e3, 0x0643 },
9741 { 0x05e4, 0x0644 },
9742 { 0x05e5, 0x0645 },
9743 { 0x05e6, 0x0646 },
9744 { 0x05e7, 0x0647 },
9745 { 0x05e8, 0x0648 },
9746 { 0x05e9, 0x0649 },
9747 { 0x05ea, 0x064a },
9748 { 0x05eb, 0x064b },
9749 { 0x05ec, 0x064c },
9750 { 0x05ed, 0x064d },
9751 { 0x05ee, 0x064e },
9752 { 0x05ef, 0x064f },
9753 { 0x05f0, 0x0650 },
9754 { 0x05f1, 0x0651 },
9755 { 0x05f2, 0x0652 },
9756 { 0x06a1, 0x0452 },
9757 { 0x06a2, 0x0453 },
9758 { 0x06a3, 0x0451 },
9759 { 0x06a4, 0x0454 },
9760 { 0x06a5, 0x0455 },
9761 { 0x06a6, 0x0456 },
9762 { 0x06a7, 0x0457 },
9763 { 0x06a8, 0x0458 },
9764 { 0x06a9, 0x0459 },
9765 { 0x06aa, 0x045a },
9766 { 0x06ab, 0x045b },
9767 { 0x06ac, 0x045c },
9768 { 0x06ae, 0x045e },
9769 { 0x06af, 0x045f },
9770 { 0x06b0, 0x2116 },
9771 { 0x06b1, 0x0402 },
9772 { 0x06b2, 0x0403 },
9773 { 0x06b3, 0x0401 },
9774 { 0x06b4, 0x0404 },
9775 { 0x06b5, 0x0405 },
9776 { 0x06b6, 0x0406 },
9777 { 0x06b7, 0x0407 },
9778 { 0x06b8, 0x0408 },
9779 { 0x06b9, 0x0409 },
9780 { 0x06ba, 0x040a },
9781 { 0x06bb, 0x040b },
9782 { 0x06bc, 0x040c },
9783 { 0x06be, 0x040e },
9784 { 0x06bf, 0x040f },
9785 { 0x06c0, 0x044e },
9786 { 0x06c1, 0x0430 },
9787 { 0x06c2, 0x0431 },
9788 { 0x06c3, 0x0446 },
9789 { 0x06c4, 0x0434 },
9790 { 0x06c5, 0x0435 },
9791 { 0x06c6, 0x0444 },
9792 { 0x06c7, 0x0433 },
9793 { 0x06c8, 0x0445 },
9794 { 0x06c9, 0x0438 },
9795 { 0x06ca, 0x0439 },
9796 { 0x06cb, 0x043a },
9797 { 0x06cc, 0x043b },
9798 { 0x06cd, 0x043c },
9799 { 0x06ce, 0x043d },
9800 { 0x06cf, 0x043e },
9801 { 0x06d0, 0x043f },
9802 { 0x06d1, 0x044f },
9803 { 0x06d2, 0x0440 },
9804 { 0x06d3, 0x0441 },
9805 { 0x06d4, 0x0442 },
9806 { 0x06d5, 0x0443 },
9807 { 0x06d6, 0x0436 },
9808 { 0x06d7, 0x0432 },
9809 { 0x06d8, 0x044c },
9810 { 0x06d9, 0x044b },
9811 { 0x06da, 0x0437 },
9812 { 0x06db, 0x0448 },
9813 { 0x06dc, 0x044d },
9814 { 0x06dd, 0x0449 },
9815 { 0x06de, 0x0447 },
9816 { 0x06df, 0x044a },
9817 { 0x06e0, 0x042e },
9818 { 0x06e1, 0x0410 },
9819 { 0x06e2, 0x0411 },
9820 { 0x06e3, 0x0426 },
9821 { 0x06e4, 0x0414 },
9822 { 0x06e5, 0x0415 },
9823 { 0x06e6, 0x0424 },
9824 { 0x06e7, 0x0413 },
9825 { 0x06e8, 0x0425 },
9826 { 0x06e9, 0x0418 },
9827 { 0x06ea, 0x0419 },
9828 { 0x06eb, 0x041a },
9829 { 0x06ec, 0x041b },
9830 { 0x06ed, 0x041c },
9831 { 0x06ee, 0x041d },
9832 { 0x06ef, 0x041e },
9833 { 0x06f0, 0x041f },
9834 { 0x06f1, 0x042f },
9835 { 0x06f2, 0x0420 },
9836 { 0x06f3, 0x0421 },
9837 { 0x06f4, 0x0422 },
9838 { 0x06f5, 0x0423 },
9839 { 0x06f6, 0x0416 },
9840 { 0x06f7, 0x0412 },
9841 { 0x06f8, 0x042c },
9842 { 0x06f9, 0x042b },
9843 { 0x06fa, 0x0417 },
9844 { 0x06fb, 0x0428 },
9845 { 0x06fc, 0x042d },
9846 { 0x06fd, 0x0429 },
9847 { 0x06fe, 0x0427 },
9848 { 0x06ff, 0x042a },
9849 { 0x07a1, 0x0386 },
9850 { 0x07a2, 0x0388 },
9851 { 0x07a3, 0x0389 },
9852 { 0x07a4, 0x038a },
9853 { 0x07a5, 0x03aa },
9854 { 0x07a7, 0x038c },
9855 { 0x07a8, 0x038e },
9856 { 0x07a9, 0x03ab },
9857 { 0x07ab, 0x038f },
9858 { 0x07ae, 0x0385 },
9859 { 0x07af, 0x2015 },
9860 { 0x07b1, 0x03ac },
9861 { 0x07b2, 0x03ad },
9862 { 0x07b3, 0x03ae },
9863 { 0x07b4, 0x03af },
9864 { 0x07b5, 0x03ca },
9865 { 0x07b6, 0x0390 },
9866 { 0x07b7, 0x03cc },
9867 { 0x07b8, 0x03cd },
9868 { 0x07b9, 0x03cb },
9869 { 0x07ba, 0x03b0 },
9870 { 0x07bb, 0x03ce },
9871 { 0x07c1, 0x0391 },
9872 { 0x07c2, 0x0392 },
9873 { 0x07c3, 0x0393 },
9874 { 0x07c4, 0x0394 },
9875 { 0x07c5, 0x0395 },
9876 { 0x07c6, 0x0396 },
9877 { 0x07c7, 0x0397 },
9878 { 0x07c8, 0x0398 },
9879 { 0x07c9, 0x0399 },
9880 { 0x07ca, 0x039a },
9881 { 0x07cb, 0x039b },
9882 { 0x07cc, 0x039c },
9883 { 0x07cd, 0x039d },
9884 { 0x07ce, 0x039e },
9885 { 0x07cf, 0x039f },
9886 { 0x07d0, 0x03a0 },
9887 { 0x07d1, 0x03a1 },
9888 { 0x07d2, 0x03a3 },
9889 { 0x07d4, 0x03a4 },
9890 { 0x07d5, 0x03a5 },
9891 { 0x07d6, 0x03a6 },
9892 { 0x07d7, 0x03a7 },
9893 { 0x07d8, 0x03a8 },
9894 { 0x07d9, 0x03a9 },
9895 { 0x07e1, 0x03b1 },
9896 { 0x07e2, 0x03b2 },
9897 { 0x07e3, 0x03b3 },
9898 { 0x07e4, 0x03b4 },
9899 { 0x07e5, 0x03b5 },
9900 { 0x07e6, 0x03b6 },
9901 { 0x07e7, 0x03b7 },
9902 { 0x07e8, 0x03b8 },
9903 { 0x07e9, 0x03b9 },
9904 { 0x07ea, 0x03ba },
9905 { 0x07eb, 0x03bb },
9906 { 0x07ec, 0x03bc },
9907 { 0x07ed, 0x03bd },
9908 { 0x07ee, 0x03be },
9909 { 0x07ef, 0x03bf },
9910 { 0x07f0, 0x03c0 },
9911 { 0x07f1, 0x03c1 },
9912 { 0x07f2, 0x03c3 },
9913 { 0x07f3, 0x03c2 },
9914 { 0x07f4, 0x03c4 },
9915 { 0x07f5, 0x03c5 },
9916 { 0x07f6, 0x03c6 },
9917 { 0x07f7, 0x03c7 },
9918 { 0x07f8, 0x03c8 },
9919 { 0x07f9, 0x03c9 },
9920 { 0x08a1, 0x23b7 },
9921 { 0x08a2, 0x250c },
9922 { 0x08a3, 0x2500 },
9923 { 0x08a4, 0x2320 },
9924 { 0x08a5, 0x2321 },
9925 { 0x08a6, 0x2502 },
9926 { 0x08a7, 0x23a1 },
9927 { 0x08a8, 0x23a3 },
9928 { 0x08a9, 0x23a4 },
9929 { 0x08aa, 0x23a6 },
9930 { 0x08ab, 0x239b },
9931 { 0x08ac, 0x239d },
9932 { 0x08ad, 0x239e },
9933 { 0x08ae, 0x23a0 },
9934 { 0x08af, 0x23a8 },
9935 { 0x08b0, 0x23ac },
9936 { 0x08bc, 0x2264 },
9937 { 0x08bd, 0x2260 },
9938 { 0x08be, 0x2265 },
9939 { 0x08bf, 0x222b },
9940 { 0x08c0, 0x2234 },
9941 { 0x08c1, 0x221d },
9942 { 0x08c2, 0x221e },
9943 { 0x08c5, 0x2207 },
9944 { 0x08c8, 0x223c },
9945 { 0x08c9, 0x2243 },
9946 { 0x08cd, 0x21d4 },
9947 { 0x08ce, 0x21d2 },
9948 { 0x08cf, 0x2261 },
9949 { 0x08d6, 0x221a },
9950 { 0x08da, 0x2282 },
9951 { 0x08db, 0x2283 },
9952 { 0x08dc, 0x2229 },
9953 { 0x08dd, 0x222a },
9954 { 0x08de, 0x2227 },
9955 { 0x08df, 0x2228 },
9956 { 0x08ef, 0x2202 },
9957 { 0x08f6, 0x0192 },
9958 { 0x08fb, 0x2190 },
9959 { 0x08fc, 0x2191 },
9960 { 0x08fd, 0x2192 },
9961 { 0x08fe, 0x2193 },
9962 { 0x09e0, 0x25c6 },
9963 { 0x09e1, 0x2592 },
9964 { 0x09e2, 0x2409 },
9965 { 0x09e3, 0x240c },
9966 { 0x09e4, 0x240d },
9967 { 0x09e5, 0x240a },
9968 { 0x09e8, 0x2424 },
9969 { 0x09e9, 0x240b },
9970 { 0x09ea, 0x2518 },
9971 { 0x09eb, 0x2510 },
9972 { 0x09ec, 0x250c },
9973 { 0x09ed, 0x2514 },
9974 { 0x09ee, 0x253c },
9975 { 0x09ef, 0x23ba },
9976 { 0x09f0, 0x23bb },
9977 { 0x09f1, 0x2500 },
9978 { 0x09f2, 0x23bc },
9979 { 0x09f3, 0x23bd },
9980 { 0x09f4, 0x251c },
9981 { 0x09f5, 0x2524 },
9982 { 0x09f6, 0x2534 },
9983 { 0x09f7, 0x252c },
9984 { 0x09f8, 0x2502 },
9985 { 0x0aa1, 0x2003 },
9986 { 0x0aa2, 0x2002 },
9987 { 0x0aa3, 0x2004 },
9988 { 0x0aa4, 0x2005 },
9989 { 0x0aa5, 0x2007 },
9990 { 0x0aa6, 0x2008 },
9991 { 0x0aa7, 0x2009 },
9992 { 0x0aa8, 0x200a },
9993 { 0x0aa9, 0x2014 },
9994 { 0x0aaa, 0x2013 },
9995 { 0x0aae, 0x2026 },
9996 { 0x0aaf, 0x2025 },
9997 { 0x0ab0, 0x2153 },
9998 { 0x0ab1, 0x2154 },
9999 { 0x0ab2, 0x2155 },
10000 { 0x0ab3, 0x2156 },
10001 { 0x0ab4, 0x2157 },
10002 { 0x0ab5, 0x2158 },
10003 { 0x0ab6, 0x2159 },
10004 { 0x0ab7, 0x215a },
10005 { 0x0ab8, 0x2105 },
10006 { 0x0abb, 0x2012 },
10007 { 0x0abc, 0x2329 },
10008 { 0x0abe, 0x232a },
10009 { 0x0ac3, 0x215b },
10010 { 0x0ac4, 0x215c },
10011 { 0x0ac5, 0x215d },
10012 { 0x0ac6, 0x215e },
10013 { 0x0ac9, 0x2122 },
10014 { 0x0aca, 0x2613 },
10015 { 0x0acc, 0x25c1 },
10016 { 0x0acd, 0x25b7 },
10017 { 0x0ace, 0x25cb },
10018 { 0x0acf, 0x25af },
10019 { 0x0ad0, 0x2018 },
10020 { 0x0ad1, 0x2019 },
10021 { 0x0ad2, 0x201c },
10022 { 0x0ad3, 0x201d },
10023 { 0x0ad4, 0x211e },
10024 { 0x0ad6, 0x2032 },
10025 { 0x0ad7, 0x2033 },
10026 { 0x0ad9, 0x271d },
10027 { 0x0adb, 0x25ac },
10028 { 0x0adc, 0x25c0 },
10029 { 0x0add, 0x25b6 },
10030 { 0x0ade, 0x25cf },
10031 { 0x0adf, 0x25ae },
10032 { 0x0ae0, 0x25e6 },
10033 { 0x0ae1, 0x25ab },
10034 { 0x0ae2, 0x25ad },
10035 { 0x0ae3, 0x25b3 },
10036 { 0x0ae4, 0x25bd },
10037 { 0x0ae5, 0x2606 },
10038 { 0x0ae6, 0x2022 },
10039 { 0x0ae7, 0x25aa },
10040 { 0x0ae8, 0x25b2 },
10041 { 0x0ae9, 0x25bc },
10042 { 0x0aea, 0x261c },
10043 { 0x0aeb, 0x261e },
10044 { 0x0aec, 0x2663 },
10045 { 0x0aed, 0x2666 },
10046 { 0x0aee, 0x2665 },
10047 { 0x0af0, 0x2720 },
10048 { 0x0af1, 0x2020 },
10049 { 0x0af2, 0x2021 },
10050 { 0x0af3, 0x2713 },
10051 { 0x0af4, 0x2717 },
10052 { 0x0af5, 0x266f },
10053 { 0x0af6, 0x266d },
10054 { 0x0af7, 0x2642 },
10055 { 0x0af8, 0x2640 },
10056 { 0x0af9, 0x260e },
10057 { 0x0afa, 0x2315 },
10058 { 0x0afb, 0x2117 },
10059 { 0x0afc, 0x2038 },
10060 { 0x0afd, 0x201a },
10061 { 0x0afe, 0x201e },
10062 { 0x0ba3, 0x003c },
10063 { 0x0ba6, 0x003e },
10064 { 0x0ba8, 0x2228 },
10065 { 0x0ba9, 0x2227 },
10066 { 0x0bc0, 0x00af },
10067 { 0x0bc2, 0x22a5 },
10068 { 0x0bc3, 0x2229 },
10069 { 0x0bc4, 0x230a },
10070 { 0x0bc6, 0x005f },
10071 { 0x0bca, 0x2218 },
10072 { 0x0bcc, 0x2395 },
10073 { 0x0bce, 0x22a4 },
10074 { 0x0bcf, 0x25cb },
10075 { 0x0bd3, 0x2308 },
10076 { 0x0bd6, 0x222a },
10077 { 0x0bd8, 0x2283 },
10078 { 0x0bda, 0x2282 },
10079 { 0x0bdc, 0x22a2 },
10080 { 0x0bfc, 0x22a3 },
10081 { 0x0cdf, 0x2017 },
10082 { 0x0ce0, 0x05d0 },
10083 { 0x0ce1, 0x05d1 },
10084 { 0x0ce2, 0x05d2 },
10085 { 0x0ce3, 0x05d3 },
10086 { 0x0ce4, 0x05d4 },
10087 { 0x0ce5, 0x05d5 },
10088 { 0x0ce6, 0x05d6 },
10089 { 0x0ce7, 0x05d7 },
10090 { 0x0ce8, 0x05d8 },
10091 { 0x0ce9, 0x05d9 },
10092 { 0x0cea, 0x05da },
10093 { 0x0ceb, 0x05db },
10094 { 0x0cec, 0x05dc },
10095 { 0x0ced, 0x05dd },
10096 { 0x0cee, 0x05de },
10097 { 0x0cef, 0x05df },
10098 { 0x0cf0, 0x05e0 },
10099 { 0x0cf1, 0x05e1 },
10100 { 0x0cf2, 0x05e2 },
10101 { 0x0cf3, 0x05e3 },
10102 { 0x0cf4, 0x05e4 },
10103 { 0x0cf5, 0x05e5 },
10104 { 0x0cf6, 0x05e6 },
10105 { 0x0cf7, 0x05e7 },
10106 { 0x0cf8, 0x05e8 },
10107 { 0x0cf9, 0x05e9 },
10108 { 0x0cfa, 0x05ea },
10109 { 0x0da1, 0x0e01 },
10110 { 0x0da2, 0x0e02 },
10111 { 0x0da3, 0x0e03 },
10112 { 0x0da4, 0x0e04 },
10113 { 0x0da5, 0x0e05 },
10114 { 0x0da6, 0x0e06 },
10115 { 0x0da7, 0x0e07 },
10116 { 0x0da8, 0x0e08 },
10117 { 0x0da9, 0x0e09 },
10118 { 0x0daa, 0x0e0a },
10119 { 0x0dab, 0x0e0b },
10120 { 0x0dac, 0x0e0c },
10121 { 0x0dad, 0x0e0d },
10122 { 0x0dae, 0x0e0e },
10123 { 0x0daf, 0x0e0f },
10124 { 0x0db0, 0x0e10 },
10125 { 0x0db1, 0x0e11 },
10126 { 0x0db2, 0x0e12 },
10127 { 0x0db3, 0x0e13 },
10128 { 0x0db4, 0x0e14 },
10129 { 0x0db5, 0x0e15 },
10130 { 0x0db6, 0x0e16 },
10131 { 0x0db7, 0x0e17 },
10132 { 0x0db8, 0x0e18 },
10133 { 0x0db9, 0x0e19 },
10134 { 0x0dba, 0x0e1a },
10135 { 0x0dbb, 0x0e1b },
10136 { 0x0dbc, 0x0e1c },
10137 { 0x0dbd, 0x0e1d },
10138 { 0x0dbe, 0x0e1e },
10139 { 0x0dbf, 0x0e1f },
10140 { 0x0dc0, 0x0e20 },
10141 { 0x0dc1, 0x0e21 },
10142 { 0x0dc2, 0x0e22 },
10143 { 0x0dc3, 0x0e23 },
10144 { 0x0dc4, 0x0e24 },
10145 { 0x0dc5, 0x0e25 },
10146 { 0x0dc6, 0x0e26 },
10147 { 0x0dc7, 0x0e27 },
10148 { 0x0dc8, 0x0e28 },
10149 { 0x0dc9, 0x0e29 },
10150 { 0x0dca, 0x0e2a },
10151 { 0x0dcb, 0x0e2b },
10152 { 0x0dcc, 0x0e2c },
10153 { 0x0dcd, 0x0e2d },
10154 { 0x0dce, 0x0e2e },
10155 { 0x0dcf, 0x0e2f },
10156 { 0x0dd0, 0x0e30 },
10157 { 0x0dd1, 0x0e31 },
10158 { 0x0dd2, 0x0e32 },
10159 { 0x0dd3, 0x0e33 },
10160 { 0x0dd4, 0x0e34 },
10161 { 0x0dd5, 0x0e35 },
10162 { 0x0dd6, 0x0e36 },
10163 { 0x0dd7, 0x0e37 },
10164 { 0x0dd8, 0x0e38 },
10165 { 0x0dd9, 0x0e39 },
10166 { 0x0dda, 0x0e3a },
10167 { 0x0ddf, 0x0e3f },
10168 { 0x0de0, 0x0e40 },
10169 { 0x0de1, 0x0e41 },
10170 { 0x0de2, 0x0e42 },
10171 { 0x0de3, 0x0e43 },
10172 { 0x0de4, 0x0e44 },
10173 { 0x0de5, 0x0e45 },
10174 { 0x0de6, 0x0e46 },
10175 { 0x0de7, 0x0e47 },
10176 { 0x0de8, 0x0e48 },
10177 { 0x0de9, 0x0e49 },
10178 { 0x0dea, 0x0e4a },
10179 { 0x0deb, 0x0e4b },
10180 { 0x0dec, 0x0e4c },
10181 { 0x0ded, 0x0e4d },
10182 { 0x0df0, 0x0e50 },
10183 { 0x0df1, 0x0e51 },
10184 { 0x0df2, 0x0e52 },
10185 { 0x0df3, 0x0e53 },
10186 { 0x0df4, 0x0e54 },
10187 { 0x0df5, 0x0e55 },
10188 { 0x0df6, 0x0e56 },
10189 { 0x0df7, 0x0e57 },
10190 { 0x0df8, 0x0e58 },
10191 { 0x0df9, 0x0e59 },
10192 { 0x0ea1, 0x3131 },
10193 { 0x0ea2, 0x3132 },
10194 { 0x0ea3, 0x3133 },
10195 { 0x0ea4, 0x3134 },
10196 { 0x0ea5, 0x3135 },
10197 { 0x0ea6, 0x3136 },
10198 { 0x0ea7, 0x3137 },
10199 { 0x0ea8, 0x3138 },
10200 { 0x0ea9, 0x3139 },
10201 { 0x0eaa, 0x313a },
10202 { 0x0eab, 0x313b },
10203 { 0x0eac, 0x313c },
10204 { 0x0ead, 0x313d },
10205 { 0x0eae, 0x313e },
10206 { 0x0eaf, 0x313f },
10207 { 0x0eb0, 0x3140 },
10208 { 0x0eb1, 0x3141 },
10209 { 0x0eb2, 0x3142 },
10210 { 0x0eb3, 0x3143 },
10211 { 0x0eb4, 0x3144 },
10212 { 0x0eb5, 0x3145 },
10213 { 0x0eb6, 0x3146 },
10214 { 0x0eb7, 0x3147 },
10215 { 0x0eb8, 0x3148 },
10216 { 0x0eb9, 0x3149 },
10217 { 0x0eba, 0x314a },
10218 { 0x0ebb, 0x314b },
10219 { 0x0ebc, 0x314c },
10220 { 0x0ebd, 0x314d },
10221 { 0x0ebe, 0x314e },
10222 { 0x0ebf, 0x314f },
10223 { 0x0ec0, 0x3150 },
10224 { 0x0ec1, 0x3151 },
10225 { 0x0ec2, 0x3152 },
10226 { 0x0ec3, 0x3153 },
10227 { 0x0ec4, 0x3154 },
10228 { 0x0ec5, 0x3155 },
10229 { 0x0ec6, 0x3156 },
10230 { 0x0ec7, 0x3157 },
10231 { 0x0ec8, 0x3158 },
10232 { 0x0ec9, 0x3159 },
10233 { 0x0eca, 0x315a },
10234 { 0x0ecb, 0x315b },
10235 { 0x0ecc, 0x315c },
10236 { 0x0ecd, 0x315d },
10237 { 0x0ece, 0x315e },
10238 { 0x0ecf, 0x315f },
10239 { 0x0ed0, 0x3160 },
10240 { 0x0ed1, 0x3161 },
10241 { 0x0ed2, 0x3162 },
10242 { 0x0ed3, 0x3163 },
10243 { 0x0ed4, 0x11a8 },
10244 { 0x0ed5, 0x11a9 },
10245 { 0x0ed6, 0x11aa },
10246 { 0x0ed7, 0x11ab },
10247 { 0x0ed8, 0x11ac },
10248 { 0x0ed9, 0x11ad },
10249 { 0x0eda, 0x11ae },
10250 { 0x0edb, 0x11af },
10251 { 0x0edc, 0x11b0 },
10252 { 0x0edd, 0x11b1 },
10253 { 0x0ede, 0x11b2 },
10254 { 0x0edf, 0x11b3 },
10255 { 0x0ee0, 0x11b4 },
10256 { 0x0ee1, 0x11b5 },
10257 { 0x0ee2, 0x11b6 },
10258 { 0x0ee3, 0x11b7 },
10259 { 0x0ee4, 0x11b8 },
10260 { 0x0ee5, 0x11b9 },
10261 { 0x0ee6, 0x11ba },
10262 { 0x0ee7, 0x11bb },
10263 { 0x0ee8, 0x11bc },
10264 { 0x0ee9, 0x11bd },
10265 { 0x0eea, 0x11be },
10266 { 0x0eeb, 0x11bf },
10267 { 0x0eec, 0x11c0 },
10268 { 0x0eed, 0x11c1 },
10269 { 0x0eee, 0x11c2 },
10270 { 0x0eef, 0x316d },
10271 { 0x0ef0, 0x3171 },
10272 { 0x0ef1, 0x3178 },
10273 { 0x0ef2, 0x317f },
10274 { 0x0ef3, 0x3181 },
10275 { 0x0ef4, 0x3184 },
10276 { 0x0ef5, 0x3186 },
10277 { 0x0ef6, 0x318d },
10278 { 0x0ef7, 0x318e },
10279 { 0x0ef8, 0x11eb },
10280 { 0x0ef9, 0x11f0 },
10281 { 0x0efa, 0x11f9 },
10282 { 0x0eff, 0x20a9 },
10283 { 0x13a4, 0x20ac },
10284 { 0x13bc, 0x0152 },
10285 { 0x13bd, 0x0153 },
10286 { 0x13be, 0x0178 },
10287 { 0x20ac, 0x20ac },
10288 { 0xfe50, '`' },
10289 { 0xfe51, 0x00b4 },
10290 { 0xfe52, '^' },
10291 { 0xfe53, '~' },
10292 { 0xfe54, 0x00af },
10293 { 0xfe55, 0x02d8 },
10294 { 0xfe56, 0x02d9 },
10295 { 0xfe57, 0x00a8 },
10296 { 0xfe58, 0x02da },
10297 { 0xfe59, 0x02dd },
10298 { 0xfe5a, 0x02c7 },
10299 { 0xfe5b, 0x00b8 },
10300 { 0xfe5c, 0x02db },
10301 { 0xfe5d, 0x037a },
10302 { 0xfe5e, 0x309b },
10303 { 0xfe5f, 0x309c },
10304 { 0xfe63, '/' },
10305 { 0xfe64, 0x02bc },
10306 { 0xfe65, 0x02bd },
10307 { 0xfe66, 0x02f5 },
10308 { 0xfe67, 0x02f3 },
10309 { 0xfe68, 0x02cd },
10310 { 0xfe69, 0xa788 },
10311 { 0xfe6a, 0x02f7 },
10312 { 0xfe6e, ',' },
10313 { 0xfe6f, 0x00a4 },
10314 { 0xfe80, 'a' }, /* XK_dead_a */
10315 { 0xfe81, 'A' }, /* XK_dead_A */
10316 { 0xfe82, 'e' }, /* XK_dead_e */
10317 { 0xfe83, 'E' }, /* XK_dead_E */
10318 { 0xfe84, 'i' }, /* XK_dead_i */
10319 { 0xfe85, 'I' }, /* XK_dead_I */
10320 { 0xfe86, 'o' }, /* XK_dead_o */
10321 { 0xfe87, 'O' }, /* XK_dead_O */
10322 { 0xfe88, 'u' }, /* XK_dead_u */
10323 { 0xfe89, 'U' }, /* XK_dead_U */
10324 { 0xfe8a, 0x0259 },
10325 { 0xfe8b, 0x018f },
10326 { 0xfe8c, 0x00b5 },
10327 { 0xfe90, '_' },
10328 { 0xfe91, 0x02c8 },
10329 { 0xfe92, 0x02cc },
10330 { 0xff80 /*XKB_KEY_KP_Space*/, ' ' },
10331 { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 },
10332 { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 },
10333 { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 },
10334 { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 },
10335 { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 },
10336 { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 },
10337 { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 },
10338 { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 },
10339 { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 },
10340 { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 },
10341 { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' },
10342 { 0xffab /*XKB_KEY_KP_Add*/, '+' },
10343 { 0xffac /*XKB_KEY_KP_Separator*/, ',' },
10344 { 0xffad /*XKB_KEY_KP_Subtract*/, '-' },
10345 { 0xffae /*XKB_KEY_KP_Decimal*/, '.' },
10346 { 0xffaf /*XKB_KEY_KP_Divide*/, '/' },
10347 { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 },
10348 { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 },
10349 { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 },
10350 { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 },
10351 { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 },
10352 { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 },
10353 { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 },
10354 { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 },
10355 { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 },
10356 { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 },
10357 { 0xffbd /*XKB_KEY_KP_Equal*/, '=' }
10358};
10359
10360_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) {
10361 _SOKOL_UNUSED(display);
10362 _sapp.x11.error_code = event->error_code;
10363 return 0;
10364}
10365
10366_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) {
10367 _sapp.x11.error_code = Success;
10368 XSetErrorHandler(_sapp_x11_error_handler);
10369}
10370
10371_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) {
10372 XSync(_sapp.x11.display, False);
10373 XSetErrorHandler(NULL);
10374}
10375
10376_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) {
10377 _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False);
10378 _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False);
10379 _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False);
10380 _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False);
10381 _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False);
10382 _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False);
10383 _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False);
10384 _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False);
10385 _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False);
10386 if (_sapp.drop.enabled) {
10387 _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False);
10388 _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False);
10389 _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False);
10390 _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False);
10391 _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False);
10392 _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False);
10393 _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False);
10394 _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False);
10395 _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False);
10396 _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False);
10397 }
10398
10399 /* check Xi extension for raw mouse input */
10400 if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) {
10401 _sapp.x11.xi.major = 2;
10402 _sapp.x11.xi.minor = 0;
10403 if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) {
10404 _sapp.x11.xi.available = true;
10405 }
10406 }
10407}
10408
10409_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) {
10410 /* from GLFW:
10411
10412 NOTE: Default to the display-wide DPI as we don't currently have a policy
10413 for which monitor a window is considered to be on
10414
10415 _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) *
10416 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen);
10417
10418 NOTE: Basing the scale on Xft.dpi where available should provide the most
10419 consistent user experience (matches Qt, Gtk, etc), although not
10420 always the most accurate one
10421 */
10422 bool dpi_ok = false;
10423 char* rms = XResourceManagerString(_sapp.x11.display);
10424 if (rms) {
10425 XrmDatabase db = XrmGetStringDatabase(rms);
10426 if (db) {
10427 XrmValue value;
10428 char* type = NULL;
10429 if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) {
10430 if (type && strcmp(type, "String") == 0) {
10431 _sapp.x11.dpi = atof(value.addr);
10432 dpi_ok = true;
10433 }
10434 }
10435 XrmDestroyDatabase(db);
10436 }
10437 }
10438 // fallback if querying DPI had failed: assume the standard DPI 96.0f
10439 if (!dpi_ok) {
10440 _sapp.x11.dpi = 96.0f;
10441 SAPP_LOG("sokol_app.h: failed to query system dpi value, assuming default 96.0");
10442 }
10443}
10444
10445#if defined(_SAPP_GLX)
10446
10447_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) {
10448 SOKOL_ASSERT(ext);
10449 const char* start = extensions;
10450 while (true) {
10451 const char* where = strstr(start, ext);
10452 if (!where) {
10453 return false;
10454 }
10455 const char* terminator = where + strlen(ext);
10456 if ((where == start) || (*(where - 1) == ' ')) {
10457 if (*terminator == ' ' || *terminator == '\0') {
10458 break;
10459 }
10460 }
10461 start = terminator;
10462 }
10463 return true;
10464}
10465
10466_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) {
10467 if (extensions) {
10468 return _sapp_glx_has_ext(ext, extensions);
10469 }
10470 else {
10471 return false;
10472 }
10473}
10474
10475_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname)
10476{
10477 if (_sapp.glx.GetProcAddress) {
10478 return (void*) _sapp.glx.GetProcAddress(procname);
10479 }
10480 else if (_sapp.glx.GetProcAddressARB) {
10481 return (void*) _sapp.glx.GetProcAddressARB(procname);
10482 }
10483 else {
10484 return dlsym(_sapp.glx.libgl, procname);
10485 }
10486}
10487
10488_SOKOL_PRIVATE void _sapp_glx_init() {
10489 const char* sonames[] = { "libGL.so.1", "libGL.so", 0 };
10490 for (int i = 0; sonames[i]; i++) {
10491 _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL);
10492 if (_sapp.glx.libgl) {
10493 break;
10494 }
10495 }
10496 if (!_sapp.glx.libgl) {
10497 _sapp_fail("GLX: failed to load libGL");
10498 }
10499 _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs");
10500 _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib");
10501 _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString");
10502 _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension");
10503 _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion");
10504 _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext");
10505 _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent");
10506 _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers");
10507 _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString");
10508 _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow");
10509 _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow");
10510 _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress");
10511 _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB");
10512 _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig");
10513 if (!_sapp.glx.GetFBConfigs ||
10514 !_sapp.glx.GetFBConfigAttrib ||
10515 !_sapp.glx.GetClientString ||
10516 !_sapp.glx.QueryExtension ||
10517 !_sapp.glx.QueryVersion ||
10518 !_sapp.glx.DestroyContext ||
10519 !_sapp.glx.MakeCurrent ||
10520 !_sapp.glx.SwapBuffers ||
10521 !_sapp.glx.QueryExtensionsString ||
10522 !_sapp.glx.CreateWindow ||
10523 !_sapp.glx.DestroyWindow ||
10524 !_sapp.glx.GetProcAddress ||
10525 !_sapp.glx.GetProcAddressARB ||
10526 !_sapp.glx.GetVisualFromFBConfig)
10527 {
10528 _sapp_fail("GLX: failed to load required entry points");
10529 }
10530
10531 if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) {
10532 _sapp_fail("GLX: GLX extension not found");
10533 }
10534 if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) {
10535 _sapp_fail("GLX: Failed to query GLX version");
10536 }
10537 if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) {
10538 _sapp_fail("GLX: GLX version 1.3 is required");
10539 }
10540 const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen);
10541 if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) {
10542 _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT");
10543 _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT;
10544 }
10545 if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) {
10546 _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA");
10547 _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA;
10548 }
10549 _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts);
10550 if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) {
10551 _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB");
10552 _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB;
10553 }
10554 _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts);
10555}
10556
10557_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) {
10558 int value;
10559 _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value);
10560 return value;
10561}
10562
10563_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() {
10564 GLXFBConfig* native_configs;
10565 _sapp_gl_fbconfig* usable_configs;
10566 const _sapp_gl_fbconfig* closest;
10567 int i, native_count, usable_count;
10568 const char* vendor;
10569 bool trust_window_bit = true;
10570
10571 /* HACK: This is a (hopefully temporary) workaround for Chromium
10572 (VirtualBox GL) not setting the window bit on any GLXFBConfigs
10573 */
10574 vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR);
10575 if (vendor && strcmp(vendor, "Chromium") == 0) {
10576 trust_window_bit = false;
10577 }
10578
10579 native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count);
10580 if (!native_configs || !native_count) {
10581 _sapp_fail("GLX: No GLXFBConfigs returned");
10582 }
10583
10584 usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig));
10585 usable_count = 0;
10586 for (i = 0; i < native_count; i++) {
10587 const GLXFBConfig n = native_configs[i];
10588 _sapp_gl_fbconfig* u = usable_configs + usable_count;
10589 _sapp_gl_init_fbconfig(u);
10590
10591 /* Only consider RGBA GLXFBConfigs */
10592 if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) {
10593 continue;
10594 }
10595 /* Only consider window GLXFBConfigs */
10596 if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) {
10597 if (trust_window_bit) {
10598 continue;
10599 }
10600 }
10601 u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE);
10602 u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE);
10603 u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE);
10604 u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE);
10605 u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE);
10606 u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE);
10607 if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) {
10608 u->doublebuffer = true;
10609 }
10610 if (_sapp.glx.ARB_multisample) {
10611 u->samples = _sapp_glx_attrib(n, GLX_SAMPLES);
10612 }
10613 u->handle = (uintptr_t) n;
10614 usable_count++;
10615 }
10616 _sapp_gl_fbconfig desired;
10617 _sapp_gl_init_fbconfig(&desired);
10618 desired.red_bits = 8;
10619 desired.green_bits = 8;
10620 desired.blue_bits = 8;
10621 desired.alpha_bits = 8;
10622 desired.depth_bits = 24;
10623 desired.stencil_bits = 8;
10624 desired.doublebuffer = true;
10625 desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0;
10626 closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count);
10627 GLXFBConfig result = 0;
10628 if (closest) {
10629 result = (GLXFBConfig) closest->handle;
10630 }
10631 XFree(native_configs);
10632 _sapp_free(usable_configs);
10633 return result;
10634}
10635
10636_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) {
10637 GLXFBConfig native = _sapp_glx_choosefbconfig();
10638 if (0 == native) {
10639 _sapp_fail("GLX: Failed to find a suitable GLXFBConfig");
10640 }
10641 XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native);
10642 if (!result) {
10643 _sapp_fail("GLX: Failed to retrieve Visual for GLXFBConfig");
10644 }
10645 *visual = result->visual;
10646 *depth = result->depth;
10647 XFree(result);
10648}
10649
10650_SOKOL_PRIVATE void _sapp_glx_create_context(void) {
10651 GLXFBConfig native = _sapp_glx_choosefbconfig();
10652 if (0 == native){
10653 _sapp_fail("GLX: Failed to find a suitable GLXFBConfig (2)");
10654 }
10655 if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) {
10656 _sapp_fail("GLX: ARB_create_context and ARB_create_context_profile required");
10657 }
10658 _sapp_x11_grab_error_handler();
10659 const int attribs[] = {
10660 GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version,
10661 GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version,
10662 GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
10663 GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
10664 0, 0
10665 };
10666 _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs);
10667 if (!_sapp.glx.ctx) {
10668 _sapp_fail("GLX: failed to create GL context");
10669 }
10670 _sapp_x11_release_error_handler();
10671 _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL);
10672 if (!_sapp.glx.window) {
10673 _sapp_fail("GLX: failed to create window");
10674 }
10675}
10676
10677_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) {
10678 if (_sapp.glx.window) {
10679 _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window);
10680 _sapp.glx.window = 0;
10681 }
10682 if (_sapp.glx.ctx) {
10683 _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx);
10684 _sapp.glx.ctx = 0;
10685 }
10686}
10687
10688_SOKOL_PRIVATE void _sapp_glx_make_current(void) {
10689 _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx);
10690}
10691
10692_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) {
10693 _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window);
10694}
10695
10696_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) {
10697 _sapp_glx_make_current();
10698 if (_sapp.glx.EXT_swap_control) {
10699 _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval);
10700 }
10701 else if (_sapp.glx.MESA_swap_control) {
10702 _sapp.glx.SwapIntervalMESA(interval);
10703 }
10704}
10705
10706#endif /* _SAPP_GLX */
10707
10708_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) {
10709 XEvent event;
10710 _sapp_clear(&event, sizeof(event));
10711
10712 event.type = ClientMessage;
10713 event.xclient.window = _sapp.x11.window;
10714 event.xclient.format = 32;
10715 event.xclient.message_type = type;
10716 event.xclient.data.l[0] = a;
10717 event.xclient.data.l[1] = b;
10718 event.xclient.data.l[2] = c;
10719 event.xclient.data.l[3] = d;
10720 event.xclient.data.l[4] = e;
10721
10722 XSendEvent(_sapp.x11.display, _sapp.x11.root,
10723 False,
10724 SubstructureNotifyMask | SubstructureRedirectMask,
10725 &event);
10726}
10727
10728_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) {
10729 XWindowAttributes attribs;
10730 XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs);
10731 _sapp.window_width = attribs.width;
10732 _sapp.window_height = attribs.height;
10733 _sapp.framebuffer_width = _sapp.window_width;
10734 _sapp.framebuffer_height = _sapp.window_height;
10735}
10736
10737_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) {
10738 /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */
10739 if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) {
10740 if (enable) {
10741 const int _NET_WM_STATE_ADD = 1;
10742 _sapp_x11_send_event(_sapp.x11.NET_WM_STATE,
10743 _NET_WM_STATE_ADD,
10744 _sapp.x11.NET_WM_STATE_FULLSCREEN,
10745 0, 1, 0);
10746 }
10747 else {
10748 const int _NET_WM_STATE_REMOVE = 0;
10749 _sapp_x11_send_event(_sapp.x11.NET_WM_STATE,
10750 _NET_WM_STATE_REMOVE,
10751 _sapp.x11.NET_WM_STATE_FULLSCREEN,
10752 0, 1, 0);
10753 }
10754 }
10755 XFlush(_sapp.x11.display);
10756}
10757
10758_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) {
10759 SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor);
10760 const int w = 16;
10761 const int h = 16;
10762 XcursorImage* img = XcursorImageCreate(w, h);
10763 SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels);
10764 img->xhot = 0;
10765 img->yhot = 0;
10766 const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel);
10767 _sapp_clear(img->pixels, num_bytes);
10768 _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img);
10769 XcursorImageDestroy(img);
10770}
10771
10772 _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) {
10773 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
10774 SOKOL_ASSERT(_sapp.x11.display);
10775 if (theme) {
10776 XcursorImage* img = XcursorLibraryLoadImage(name, theme, size);
10777 if (img) {
10778 _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img);
10779 XcursorImageDestroy(img);
10780 }
10781 }
10782 if (0 == _sapp.x11.cursors[cursor]) {
10783 _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native);
10784 }
10785}
10786
10787_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) {
10788 SOKOL_ASSERT(_sapp.x11.display);
10789 const char* cursor_theme = XcursorGetTheme(_sapp.x11.display);
10790 const int size = XcursorGetDefaultSize(_sapp.x11.display);
10791 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr);
10792 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm);
10793 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair);
10794 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2);
10795 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow);
10796 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow);
10797 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0);
10798 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0);
10799 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur);
10800 _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0);
10801 _sapp_x11_create_hidden_cursor();
10802}
10803
10804_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) {
10805 SOKOL_ASSERT(_sapp.x11.display);
10806 if (_sapp.x11.hidden_cursor) {
10807 XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor);
10808 _sapp.x11.hidden_cursor = 0;
10809 }
10810 for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) {
10811 if (_sapp.x11.cursors[i]) {
10812 XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]);
10813 _sapp.x11.cursors[i] = 0;
10814 }
10815 }
10816}
10817
10818_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) {
10819 _sapp.fullscreen = !_sapp.fullscreen;
10820 _sapp_x11_set_fullscreen(_sapp.fullscreen);
10821 _sapp_x11_query_window_size();
10822}
10823
10824_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) {
10825 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
10826 if (shown) {
10827 if (_sapp.x11.cursors[cursor]) {
10828 XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]);
10829 }
10830 else {
10831 XUndefineCursor(_sapp.x11.display, _sapp.x11.window);
10832 }
10833 }
10834 else {
10835 XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor);
10836 }
10837 XFlush(_sapp.x11.display);
10838}
10839
10840_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) {
10841 if (lock == _sapp.mouse.locked) {
10842 return;
10843 }
10844 _sapp.mouse.dx = 0.0f;
10845 _sapp.mouse.dy = 0.0f;
10846 _sapp.mouse.locked = lock;
10847 if (_sapp.mouse.locked) {
10848 if (_sapp.x11.xi.available) {
10849 XIEventMask em;
10850 unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro
10851 em.deviceid = XIAllMasterDevices;
10852 em.mask_len = sizeof(mask);
10853 em.mask = mask;
10854 XISetMask(mask, XI_RawMotion);
10855 XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1);
10856 }
10857 XGrabPointer(_sapp.x11.display, // display
10858 _sapp.x11.window, // grab_window
10859 True, // owner_events
10860 ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask
10861 GrabModeAsync, // pointer_mode
10862 GrabModeAsync, // keyboard_mode
10863 _sapp.x11.window, // confine_to
10864 _sapp.x11.hidden_cursor, // cursor
10865 CurrentTime); // time
10866 }
10867 else {
10868 if (_sapp.x11.xi.available) {
10869 XIEventMask em;
10870 unsigned char mask[] = { 0 };
10871 em.deviceid = XIAllMasterDevices;
10872 em.mask_len = sizeof(mask);
10873 em.mask = mask;
10874 XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1);
10875 }
10876 XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y);
10877 XUngrabPointer(_sapp.x11.display, CurrentTime);
10878 }
10879 XFlush(_sapp.x11.display);
10880}
10881
10882_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) {
10883 Xutf8SetWMProperties(_sapp.x11.display,
10884 _sapp.x11.window,
10885 _sapp.window_title, _sapp.window_title,
10886 NULL, 0, NULL, NULL, NULL);
10887 XChangeProperty(_sapp.x11.display, _sapp.x11.window,
10888 _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8,
10889 PropModeReplace,
10890 (unsigned char*)_sapp.window_title,
10891 strlen(_sapp.window_title));
10892 XChangeProperty(_sapp.x11.display, _sapp.x11.window,
10893 _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8,
10894 PropModeReplace,
10895 (unsigned char*)_sapp.window_title,
10896 strlen(_sapp.window_title));
10897 XFlush(_sapp.x11.display);
10898}
10899
10900_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) {
10901 SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES));
10902 int long_count = 0;
10903 for (int i = 0; i < num_images; i++) {
10904 const sapp_image_desc* img_desc = &icon_desc->images[i];
10905 long_count += 2 + (img_desc->width * img_desc->height);
10906 }
10907 long* icon_data = (long*) _sapp_malloc_clear((size_t)long_count * sizeof(long));
10908 SOKOL_ASSERT(icon_data);
10909 long* dst = icon_data;
10910 for (int img_index = 0; img_index < num_images; img_index++) {
10911 const sapp_image_desc* img_desc = &icon_desc->images[img_index];
10912 const uint8_t* src = (const uint8_t*) img_desc->pixels.ptr;
10913 *dst++ = img_desc->width;
10914 *dst++ = img_desc->height;
10915 const int num_pixels = img_desc->width * img_desc->height;
10916 for (int pixel_index = 0; pixel_index < num_pixels; pixel_index++) {
10917 *dst++ = ((long)(src[pixel_index * 4 + 0]) << 16) |
10918 ((long)(src[pixel_index * 4 + 1]) << 8) |
10919 ((long)(src[pixel_index * 4 + 2]) << 0) |
10920 ((long)(src[pixel_index * 4 + 3]) << 24);
10921 }
10922 }
10923 XChangeProperty(_sapp.x11.display, _sapp.x11.window,
10924 _sapp.x11.NET_WM_ICON,
10925 XA_CARDINAL, 32,
10926 PropModeReplace,
10927 (unsigned char*)icon_data,
10928 long_count);
10929 _sapp_free(icon_data);
10930 XFlush(_sapp.x11.display);
10931}
10932
10933_SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) {
10934 _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone);
10935 XSetWindowAttributes wa;
10936 _sapp_clear(&wa, sizeof(wa));
10937 const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask;
10938 wa.colormap = _sapp.x11.colormap;
10939 wa.border_pixel = 0;
10940 wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
10941 PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
10942 ExposureMask | FocusChangeMask | VisibilityChangeMask |
10943 EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
10944
10945 int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen);
10946 int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen);
10947 int window_width = _sapp.window_width;
10948 int window_height = _sapp.window_height;
10949 if (0 == window_width) {
10950 window_width = (display_width * 4) / 5;
10951 }
10952 if (0 == window_height) {
10953 window_height = (display_height * 4) / 5;
10954 }
10955 int window_xpos = (display_width - window_width) / 2;
10956 int window_ypos = (display_height - window_height) / 2;
10957 if (window_xpos < 0) {
10958 window_xpos = 0;
10959 }
10960 if (window_ypos < 0) {
10961 window_ypos = 0;
10962 }
10963 _sapp_x11_grab_error_handler();
10964 _sapp.x11.window = XCreateWindow(_sapp.x11.display,
10965 _sapp.x11.root,
10966 window_xpos,
10967 window_ypos,
10968 (uint32_t)window_width,
10969 (uint32_t)window_height,
10970 0, /* border width */
10971 depth, /* color depth */
10972 InputOutput,
10973 visual,
10974 wamask,
10975 &wa);
10976 _sapp_x11_release_error_handler();
10977 if (!_sapp.x11.window) {
10978 _sapp_fail("X11: Failed to create window");
10979 }
10980 Atom protocols[] = {
10981 _sapp.x11.WM_DELETE_WINDOW
10982 };
10983 XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1);
10984
10985 XSizeHints* hints = XAllocSizeHints();
10986 hints->flags = (PWinGravity | PPosition | PSize);
10987 hints->win_gravity = StaticGravity;
10988 hints->x = window_xpos;
10989 hints->y = window_ypos;
10990 hints->width = window_width;
10991 hints->height = window_height;
10992 XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints);
10993 XFree(hints);
10994
10995 /* announce support for drag'n'drop */
10996 if (_sapp.drop.enabled) {
10997 const Atom version = _SAPP_X11_XDND_VERSION;
10998 XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1);
10999 }
11000 _sapp_x11_update_window_title();
11001 _sapp_x11_query_window_size();
11002}
11003
11004_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) {
11005 if (_sapp.x11.window) {
11006 XUnmapWindow(_sapp.x11.display, _sapp.x11.window);
11007 XDestroyWindow(_sapp.x11.display, _sapp.x11.window);
11008 _sapp.x11.window = 0;
11009 }
11010 if (_sapp.x11.colormap) {
11011 XFreeColormap(_sapp.x11.display, _sapp.x11.colormap);
11012 _sapp.x11.colormap = 0;
11013 }
11014 XFlush(_sapp.x11.display);
11015}
11016
11017_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) {
11018 XWindowAttributes wa;
11019 XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa);
11020 return wa.map_state == IsViewable;
11021}
11022
11023_SOKOL_PRIVATE void _sapp_x11_show_window(void) {
11024 if (!_sapp_x11_window_visible()) {
11025 XMapWindow(_sapp.x11.display, _sapp.x11.window);
11026 XRaiseWindow(_sapp.x11.display, _sapp.x11.window);
11027 XFlush(_sapp.x11.display);
11028 }
11029}
11030
11031_SOKOL_PRIVATE void _sapp_x11_hide_window(void) {
11032 XUnmapWindow(_sapp.x11.display, _sapp.x11.window);
11033 XFlush(_sapp.x11.display);
11034}
11035
11036_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) {
11037 Atom actualType;
11038 int actualFormat;
11039 unsigned long itemCount, bytesAfter;
11040 XGetWindowProperty(_sapp.x11.display,
11041 window,
11042 property,
11043 0,
11044 LONG_MAX,
11045 False,
11046 type,
11047 &actualType,
11048 &actualFormat,
11049 &itemCount,
11050 &bytesAfter,
11051 value);
11052 return itemCount;
11053}
11054
11055_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) {
11056 int result = WithdrawnState;
11057 struct {
11058 CARD32 state;
11059 Window icon;
11060 } *state = NULL;
11061
11062 if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) {
11063 result = (int)state->state;
11064 }
11065 if (state) {
11066 XFree(state);
11067 }
11068 return result;
11069}
11070
11071_SOKOL_PRIVATE uint32_t _sapp_x11_key_modifier_bit(sapp_keycode key) {
11072 switch (key) {
11073 case SAPP_KEYCODE_LEFT_SHIFT:
11074 case SAPP_KEYCODE_RIGHT_SHIFT:
11075 return SAPP_MODIFIER_SHIFT;
11076 case SAPP_KEYCODE_LEFT_CONTROL:
11077 case SAPP_KEYCODE_RIGHT_CONTROL:
11078 return SAPP_MODIFIER_CTRL;
11079 case SAPP_KEYCODE_LEFT_ALT:
11080 case SAPP_KEYCODE_RIGHT_ALT:
11081 return SAPP_MODIFIER_ALT;
11082 case SAPP_KEYCODE_LEFT_SUPER:
11083 case SAPP_KEYCODE_RIGHT_SUPER:
11084 return SAPP_MODIFIER_SUPER;
11085 default:
11086 return 0;
11087 }
11088}
11089
11090_SOKOL_PRIVATE uint32_t _sapp_x11_button_modifier_bit(sapp_mousebutton btn) {
11091 switch (btn) {
11092 case SAPP_MOUSEBUTTON_LEFT: return SAPP_MODIFIER_LMB;
11093 case SAPP_MOUSEBUTTON_RIGHT: return SAPP_MODIFIER_RMB;
11094 case SAPP_MOUSEBUTTON_MIDDLE: return SAPP_MODIFIER_MMB;
11095 default: return 0;
11096 }
11097}
11098
11099_SOKOL_PRIVATE uint32_t _sapp_x11_mods(uint32_t x11_mods) {
11100 uint32_t mods = 0;
11101 if (x11_mods & ShiftMask) {
11102 mods |= SAPP_MODIFIER_SHIFT;
11103 }
11104 if (x11_mods & ControlMask) {
11105 mods |= SAPP_MODIFIER_CTRL;
11106 }
11107 if (x11_mods & Mod1Mask) {
11108 mods |= SAPP_MODIFIER_ALT;
11109 }
11110 if (x11_mods & Mod4Mask) {
11111 mods |= SAPP_MODIFIER_SUPER;
11112 }
11113 if (x11_mods & Button1Mask) {
11114 mods |= SAPP_MODIFIER_LMB;
11115 }
11116 if (x11_mods & Button2Mask) {
11117 mods |= SAPP_MODIFIER_MMB;
11118 }
11119 if (x11_mods & Button3Mask) {
11120 mods |= SAPP_MODIFIER_RMB;
11121 }
11122 return mods;
11123}
11124
11125_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) {
11126 if (_sapp_events_enabled()) {
11127 _sapp_init_event(type);
11128 _sapp_call_event(&_sapp.event);
11129 }
11130}
11131
11132_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) {
11133 switch (event->xbutton.button) {
11134 case Button1: return SAPP_MOUSEBUTTON_LEFT;
11135 case Button2: return SAPP_MOUSEBUTTON_MIDDLE;
11136 case Button3: return SAPP_MOUSEBUTTON_RIGHT;
11137 default: return SAPP_MOUSEBUTTON_INVALID;
11138 }
11139}
11140
11141_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) {
11142 if (_sapp_events_enabled()) {
11143 _sapp_init_event(type);
11144 _sapp.event.mouse_button = btn;
11145 _sapp.event.modifiers = mods;
11146 _sapp_call_event(&_sapp.event);
11147 }
11148}
11149
11150_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) {
11151 if (_sapp_events_enabled()) {
11152 _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL);
11153 _sapp.event.modifiers = mods;
11154 _sapp.event.scroll_x = x;
11155 _sapp.event.scroll_y = y;
11156 _sapp_call_event(&_sapp.event);
11157 }
11158}
11159
11160_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) {
11161 if (_sapp_events_enabled()) {
11162 _sapp_init_event(type);
11163 _sapp.event.key_code = key;
11164 _sapp.event.key_repeat = repeat;
11165 _sapp.event.modifiers = mods;
11166 _sapp_call_event(&_sapp.event);
11167 /* check if a CLIPBOARD_PASTED event must be sent too */
11168 if (_sapp.clipboard.enabled &&
11169 (type == SAPP_EVENTTYPE_KEY_DOWN) &&
11170 (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) &&
11171 (_sapp.event.key_code == SAPP_KEYCODE_V))
11172 {
11173 _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
11174 _sapp_call_event(&_sapp.event);
11175 }
11176 }
11177}
11178
11179_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) {
11180 if (_sapp_events_enabled()) {
11181 _sapp_init_event(SAPP_EVENTTYPE_CHAR);
11182 _sapp.event.char_code = chr;
11183 _sapp.event.key_repeat = repeat;
11184 _sapp.event.modifiers = mods;
11185 _sapp_call_event(&_sapp.event);
11186 }
11187}
11188
11189_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) {
11190 int dummy;
11191 KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy);
11192 SOKOL_ASSERT(keysyms);
11193 KeySym keysym = keysyms[0];
11194 XFree(keysyms);
11195 switch (keysym) {
11196 case XK_Escape: return SAPP_KEYCODE_ESCAPE;
11197 case XK_Tab: return SAPP_KEYCODE_TAB;
11198 case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT;
11199 case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT;
11200 case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL;
11201 case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL;
11202 case XK_Meta_L:
11203 case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT;
11204 case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */
11205 case XK_ISO_Level3_Shift: /* AltGr on at least some machines */
11206 case XK_Meta_R:
11207 case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT;
11208 case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER;
11209 case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER;
11210 case XK_Menu: return SAPP_KEYCODE_MENU;
11211 case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK;
11212 case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK;
11213 case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN;
11214 case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK;
11215 case XK_Pause: return SAPP_KEYCODE_PAUSE;
11216 case XK_Delete: return SAPP_KEYCODE_DELETE;
11217 case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE;
11218 case XK_Return: return SAPP_KEYCODE_ENTER;
11219 case XK_Home: return SAPP_KEYCODE_HOME;
11220 case XK_End: return SAPP_KEYCODE_END;
11221 case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP;
11222 case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN;
11223 case XK_Insert: return SAPP_KEYCODE_INSERT;
11224 case XK_Left: return SAPP_KEYCODE_LEFT;
11225 case XK_Right: return SAPP_KEYCODE_RIGHT;
11226 case XK_Down: return SAPP_KEYCODE_DOWN;
11227 case XK_Up: return SAPP_KEYCODE_UP;
11228 case XK_F1: return SAPP_KEYCODE_F1;
11229 case XK_F2: return SAPP_KEYCODE_F2;
11230 case XK_F3: return SAPP_KEYCODE_F3;
11231 case XK_F4: return SAPP_KEYCODE_F4;
11232 case XK_F5: return SAPP_KEYCODE_F5;
11233 case XK_F6: return SAPP_KEYCODE_F6;
11234 case XK_F7: return SAPP_KEYCODE_F7;
11235 case XK_F8: return SAPP_KEYCODE_F8;
11236 case XK_F9: return SAPP_KEYCODE_F9;
11237 case XK_F10: return SAPP_KEYCODE_F10;
11238 case XK_F11: return SAPP_KEYCODE_F11;
11239 case XK_F12: return SAPP_KEYCODE_F12;
11240 case XK_F13: return SAPP_KEYCODE_F13;
11241 case XK_F14: return SAPP_KEYCODE_F14;
11242 case XK_F15: return SAPP_KEYCODE_F15;
11243 case XK_F16: return SAPP_KEYCODE_F16;
11244 case XK_F17: return SAPP_KEYCODE_F17;
11245 case XK_F18: return SAPP_KEYCODE_F18;
11246 case XK_F19: return SAPP_KEYCODE_F19;
11247 case XK_F20: return SAPP_KEYCODE_F20;
11248 case XK_F21: return SAPP_KEYCODE_F21;
11249 case XK_F22: return SAPP_KEYCODE_F22;
11250 case XK_F23: return SAPP_KEYCODE_F23;
11251 case XK_F24: return SAPP_KEYCODE_F24;
11252 case XK_F25: return SAPP_KEYCODE_F25;
11253
11254 case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE;
11255 case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY;
11256 case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT;
11257 case XK_KP_Add: return SAPP_KEYCODE_KP_ADD;
11258
11259 case XK_KP_Insert: return SAPP_KEYCODE_KP_0;
11260 case XK_KP_End: return SAPP_KEYCODE_KP_1;
11261 case XK_KP_Down: return SAPP_KEYCODE_KP_2;
11262 case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3;
11263 case XK_KP_Left: return SAPP_KEYCODE_KP_4;
11264 case XK_KP_Begin: return SAPP_KEYCODE_KP_5;
11265 case XK_KP_Right: return SAPP_KEYCODE_KP_6;
11266 case XK_KP_Home: return SAPP_KEYCODE_KP_7;
11267 case XK_KP_Up: return SAPP_KEYCODE_KP_8;
11268 case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9;
11269 case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL;
11270 case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL;
11271 case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER;
11272
11273 case XK_a: return SAPP_KEYCODE_A;
11274 case XK_b: return SAPP_KEYCODE_B;
11275 case XK_c: return SAPP_KEYCODE_C;
11276 case XK_d: return SAPP_KEYCODE_D;
11277 case XK_e: return SAPP_KEYCODE_E;
11278 case XK_f: return SAPP_KEYCODE_F;
11279 case XK_g: return SAPP_KEYCODE_G;
11280 case XK_h: return SAPP_KEYCODE_H;
11281 case XK_i: return SAPP_KEYCODE_I;
11282 case XK_j: return SAPP_KEYCODE_J;
11283 case XK_k: return SAPP_KEYCODE_K;
11284 case XK_l: return SAPP_KEYCODE_L;
11285 case XK_m: return SAPP_KEYCODE_M;
11286 case XK_n: return SAPP_KEYCODE_N;
11287 case XK_o: return SAPP_KEYCODE_O;
11288 case XK_p: return SAPP_KEYCODE_P;
11289 case XK_q: return SAPP_KEYCODE_Q;
11290 case XK_r: return SAPP_KEYCODE_R;
11291 case XK_s: return SAPP_KEYCODE_S;
11292 case XK_t: return SAPP_KEYCODE_T;
11293 case XK_u: return SAPP_KEYCODE_U;
11294 case XK_v: return SAPP_KEYCODE_V;
11295 case XK_w: return SAPP_KEYCODE_W;
11296 case XK_x: return SAPP_KEYCODE_X;
11297 case XK_y: return SAPP_KEYCODE_Y;
11298 case XK_z: return SAPP_KEYCODE_Z;
11299 case XK_1: return SAPP_KEYCODE_1;
11300 case XK_2: return SAPP_KEYCODE_2;
11301 case XK_3: return SAPP_KEYCODE_3;
11302 case XK_4: return SAPP_KEYCODE_4;
11303 case XK_5: return SAPP_KEYCODE_5;
11304 case XK_6: return SAPP_KEYCODE_6;
11305 case XK_7: return SAPP_KEYCODE_7;
11306 case XK_8: return SAPP_KEYCODE_8;
11307 case XK_9: return SAPP_KEYCODE_9;
11308 case XK_0: return SAPP_KEYCODE_0;
11309 case XK_space: return SAPP_KEYCODE_SPACE;
11310 case XK_minus: return SAPP_KEYCODE_MINUS;
11311 case XK_equal: return SAPP_KEYCODE_EQUAL;
11312 case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET;
11313 case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET;
11314 case XK_backslash: return SAPP_KEYCODE_BACKSLASH;
11315 case XK_semicolon: return SAPP_KEYCODE_SEMICOLON;
11316 case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE;
11317 case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT;
11318 case XK_comma: return SAPP_KEYCODE_COMMA;
11319 case XK_period: return SAPP_KEYCODE_PERIOD;
11320 case XK_slash: return SAPP_KEYCODE_SLASH;
11321 case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */
11322 default: return SAPP_KEYCODE_INVALID;
11323 }
11324}
11325
11326_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) {
11327 int min = 0;
11328 int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1;
11329 int mid;
11330
11331 /* First check for Latin-1 characters (1:1 mapping) */
11332 if ((keysym >= 0x0020 && keysym <= 0x007e) ||
11333 (keysym >= 0x00a0 && keysym <= 0x00ff))
11334 {
11335 return keysym;
11336 }
11337
11338 /* Also check for directly encoded 24-bit UCS characters */
11339 if ((keysym & 0xff000000) == 0x01000000) {
11340 return keysym & 0x00ffffff;
11341 }
11342
11343 /* Binary search in table */
11344 while (max >= min) {
11345 mid = (min + max) / 2;
11346 if (_sapp_x11_keysymtab[mid].keysym < keysym) {
11347 min = mid + 1;
11348 }
11349 else if (_sapp_x11_keysymtab[mid].keysym > keysym) {
11350 max = mid - 1;
11351 }
11352 else {
11353 return _sapp_x11_keysymtab[mid].ucs;
11354 }
11355 }
11356
11357 /* No matching Unicode value found */
11358 return -1;
11359}
11360
11361_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) {
11362 SOKOL_ASSERT(src);
11363 SOKOL_ASSERT(_sapp.drop.buffer);
11364
11365 _sapp_clear_drop_buffer();
11366 _sapp.drop.num_files = 0;
11367
11368 /*
11369 src is (potentially percent-encoded) string made of one or multiple paths
11370 separated by \r\n, each path starting with 'file://'
11371 */
11372 bool err = false;
11373 int src_count = 0;
11374 char src_chr = 0;
11375 char* dst_ptr = _sapp.drop.buffer;
11376 const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0
11377 while (0 != (src_chr = *src++)) {
11378 src_count++;
11379 char dst_chr = 0;
11380 /* check leading 'file://' */
11381 if (src_count <= 7) {
11382 if (((src_count == 1) && (src_chr != 'f')) ||
11383 ((src_count == 2) && (src_chr != 'i')) ||
11384 ((src_count == 3) && (src_chr != 'l')) ||
11385 ((src_count == 4) && (src_chr != 'e')) ||
11386 ((src_count == 5) && (src_chr != ':')) ||
11387 ((src_count == 6) && (src_chr != '/')) ||
11388 ((src_count == 7) && (src_chr != '/')))
11389 {
11390 SAPP_LOG("sokol_app.h: dropped file URI doesn't start with file://");
11391 err = true;
11392 break;
11393 }
11394 }
11395 else if (src_chr == '\r') {
11396 // skip
11397 }
11398 else if (src_chr == '\n') {
11399 src_count = 0;
11400 _sapp.drop.num_files++;
11401 // too many files is not an error
11402 if (_sapp.drop.num_files >= _sapp.drop.max_files) {
11403 break;
11404 }
11405 dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length;
11406 dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1);
11407 }
11408 else if ((src_chr == '%') && src[0] && src[1]) {
11409 // a percent-encoded byte (most likely UTF-8 multibyte sequence)
11410 const char digits[3] = { src[0], src[1], 0 };
11411 src += 2;
11412 dst_chr = (char) strtol(digits, 0, 16);
11413 }
11414 else {
11415 dst_chr = src_chr;
11416 }
11417 if (dst_chr) {
11418 // dst_end_ptr already has adjustment for terminating zero
11419 if (dst_ptr < dst_end_ptr) {
11420 *dst_ptr++ = dst_chr;
11421 }
11422 else {
11423 SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)");
11424 err = true;
11425 break;
11426 }
11427 }
11428 }
11429 if (err) {
11430 _sapp_clear_drop_buffer();
11431 _sapp.drop.num_files = 0;
11432 return false;
11433 }
11434 else {
11435 return true;
11436 }
11437}
11438
11439// XLib manual says keycodes are in the range [8, 255] inclusive.
11440// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html
11441static bool _sapp_x11_keycodes[256];
11442
11443_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) {
11444 Bool filtered = XFilterEvent(event, None);
11445 switch (event->type) {
11446 case GenericEvent:
11447 if (_sapp.mouse.locked && _sapp.x11.xi.available) {
11448 if (event->xcookie.extension == _sapp.x11.xi.major_opcode) {
11449 if (XGetEventData(_sapp.x11.display, &event->xcookie)) {
11450 if (event->xcookie.evtype == XI_RawMotion) {
11451 XIRawEvent* re = (XIRawEvent*) event->xcookie.data;
11452 if (re->valuators.mask_len) {
11453 const double* values = re->raw_values;
11454 if (XIMaskIsSet(re->valuators.mask, 0)) {
11455 _sapp.mouse.dx = (float) *values;
11456 values++;
11457 }
11458 if (XIMaskIsSet(re->valuators.mask, 1)) {
11459 _sapp.mouse.dy = (float) *values;
11460 }
11461 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state));
11462 }
11463 }
11464 XFreeEventData(_sapp.x11.display, &event->xcookie);
11465 }
11466 }
11467 }
11468 break;
11469 case FocusIn:
11470 // NOTE: ingnoring NotifyGrab and NotifyUngrab is same behaviour as GLFW
11471 if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) {
11472 _sapp_x11_app_event(SAPP_EVENTTYPE_FOCUSED);
11473 }
11474 break;
11475 case FocusOut:
11476 /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */
11477 if (_sapp.mouse.locked) {
11478 _sapp_x11_lock_mouse(false);
11479 }
11480 // NOTE: ingnoring NotifyGrab and NotifyUngrab is same behaviour as GLFW
11481 if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) {
11482 _sapp_x11_app_event(SAPP_EVENTTYPE_UNFOCUSED);
11483 }
11484 break;
11485 case KeyPress:
11486 {
11487 int keycode = (int)event->xkey.keycode;
11488 const sapp_keycode key = _sapp_x11_translate_key(keycode);
11489 bool repeat = _sapp_x11_keycodes[keycode & 0xFF];
11490 _sapp_x11_keycodes[keycode & 0xFF] = true;
11491 uint32_t mods = _sapp_x11_mods(event->xkey.state);
11492 // X11 doesn't set modifier bit on key down, so emulate that
11493 mods |= _sapp_x11_key_modifier_bit(key);
11494 if (key != SAPP_KEYCODE_INVALID) {
11495 _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods);
11496 }
11497 KeySym keysym;
11498 XLookupString(&event->xkey, NULL, 0, &keysym, NULL);
11499 int32_t chr = _sapp_x11_keysym_to_unicode(keysym);
11500 if (chr > 0) {
11501 _sapp_x11_char_event((uint32_t)chr, repeat, mods);
11502 }
11503 }
11504 break;
11505 case KeyRelease:
11506 {
11507 int keycode = (int)event->xkey.keycode;
11508 const sapp_keycode key = _sapp_x11_translate_key(keycode);
11509 _sapp_x11_keycodes[keycode & 0xFF] = false;
11510 if (key != SAPP_KEYCODE_INVALID) {
11511 uint32_t mods = _sapp_x11_mods(event->xkey.state);
11512 // X11 doesn't clear modifier bit on key up, so emulate that
11513 mods &= ~_sapp_x11_key_modifier_bit(key);
11514 _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods);
11515 }
11516 }
11517 break;
11518 case ButtonPress:
11519 {
11520 const sapp_mousebutton btn = _sapp_x11_translate_button(event);
11521 uint32_t mods = _sapp_x11_mods(event->xbutton.state);
11522 // X11 doesn't set modifier bit on button down, so emulate that
11523 mods |= _sapp_x11_button_modifier_bit(btn);
11524 if (btn != SAPP_MOUSEBUTTON_INVALID) {
11525 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods);
11526 _sapp.x11.mouse_buttons |= (1 << btn);
11527 }
11528 else {
11529 /* might be a scroll event */
11530 switch (event->xbutton.button) {
11531 case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break;
11532 case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break;
11533 case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break;
11534 case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break;
11535 }
11536 }
11537 }
11538 break;
11539 case ButtonRelease:
11540 {
11541 const sapp_mousebutton btn = _sapp_x11_translate_button(event);
11542 if (btn != SAPP_MOUSEBUTTON_INVALID) {
11543 uint32_t mods = _sapp_x11_mods(event->xbutton.state);
11544 // X11 doesn't clear modifier bit on button up, so emulate that
11545 mods &= ~_sapp_x11_button_modifier_bit(btn);
11546 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, mods);
11547 _sapp.x11.mouse_buttons &= ~(1 << btn);
11548 }
11549 }
11550 break;
11551 case EnterNotify:
11552 /* don't send enter/leave events while mouse button held down */
11553 if (0 == _sapp.x11.mouse_buttons) {
11554 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state));
11555 }
11556 break;
11557 case LeaveNotify:
11558 if (0 == _sapp.x11.mouse_buttons) {
11559 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state));
11560 }
11561 break;
11562 case MotionNotify:
11563 if (!_sapp.mouse.locked) {
11564 const float new_x = (float) event->xmotion.x;
11565 const float new_y = (float) event->xmotion.y;
11566 if (_sapp.mouse.pos_valid) {
11567 _sapp.mouse.dx = new_x - _sapp.mouse.x;
11568 _sapp.mouse.dy = new_y - _sapp.mouse.y;
11569 }
11570 _sapp.mouse.x = new_x;
11571 _sapp.mouse.y = new_y;
11572 _sapp.mouse.pos_valid = true;
11573 _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state));
11574 }
11575 break;
11576 case ConfigureNotify:
11577 if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) {
11578 _sapp.window_width = event->xconfigure.width;
11579 _sapp.window_height = event->xconfigure.height;
11580 _sapp.framebuffer_width = _sapp.window_width;
11581 _sapp.framebuffer_height = _sapp.window_height;
11582 _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED);
11583 }
11584 break;
11585 case PropertyNotify:
11586 if (event->xproperty.state == PropertyNewValue) {
11587 if (event->xproperty.atom == _sapp.x11.WM_STATE) {
11588 const int state = _sapp_x11_get_window_state();
11589 if (state != _sapp.x11.window_state) {
11590 _sapp.x11.window_state = state;
11591 if (state == IconicState) {
11592 _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED);
11593 }
11594 else if (state == NormalState) {
11595 _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED);
11596 }
11597 }
11598 }
11599 }
11600 break;
11601 case ClientMessage:
11602 if (filtered) {
11603 return;
11604 }
11605 if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) {
11606 const Atom protocol = (Atom)event->xclient.data.l[0];
11607 if (protocol == _sapp.x11.WM_DELETE_WINDOW) {
11608 _sapp.quit_requested = true;
11609 }
11610 }
11611 else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) {
11612 const bool is_list = 0 != (event->xclient.data.l[1] & 1);
11613 _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0];
11614 _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24;
11615 _sapp.x11.xdnd.format = None;
11616 if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) {
11617 return;
11618 }
11619 uint32_t count = 0;
11620 Atom* formats = 0;
11621 if (is_list) {
11622 count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats);
11623 }
11624 else {
11625 count = 3;
11626 formats = (Atom*) event->xclient.data.l + 2;
11627 }
11628 for (uint32_t i = 0; i < count; i++) {
11629 if (formats[i] == _sapp.x11.xdnd.text_uri_list) {
11630 _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list;
11631 break;
11632 }
11633 }
11634 if (is_list && formats) {
11635 XFree(formats);
11636 }
11637 }
11638 else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) {
11639 if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) {
11640 return;
11641 }
11642 Time time = CurrentTime;
11643 if (_sapp.x11.xdnd.format) {
11644 if (_sapp.x11.xdnd.version >= 1) {
11645 time = (Time)event->xclient.data.l[2];
11646 }
11647 XConvertSelection(_sapp.x11.display,
11648 _sapp.x11.xdnd.XdndSelection,
11649 _sapp.x11.xdnd.format,
11650 _sapp.x11.xdnd.XdndSelection,
11651 _sapp.x11.window,
11652 time);
11653 }
11654 else if (_sapp.x11.xdnd.version >= 2) {
11655 XEvent reply;
11656 _sapp_clear(&reply, sizeof(reply));
11657 reply.type = ClientMessage;
11658 reply.xclient.window = _sapp.x11.window;
11659 reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished;
11660 reply.xclient.format = 32;
11661 reply.xclient.data.l[0] = (long)_sapp.x11.window;
11662 reply.xclient.data.l[1] = 0; // drag was rejected
11663 reply.xclient.data.l[2] = None;
11664 XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply);
11665 XFlush(_sapp.x11.display);
11666 }
11667 }
11668 else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) {
11669 /* drag operation has moved over the window
11670 FIXME: we could track the mouse position here, but
11671 this isn't implemented on other platforms either so far
11672 */
11673 if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) {
11674 return;
11675 }
11676 XEvent reply;
11677 _sapp_clear(&reply, sizeof(reply));
11678 reply.type = ClientMessage;
11679 reply.xclient.window = _sapp.x11.xdnd.source;
11680 reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus;
11681 reply.xclient.format = 32;
11682 reply.xclient.data.l[0] = (long)_sapp.x11.window;
11683 if (_sapp.x11.xdnd.format) {
11684 /* reply that we are ready to copy the dragged data */
11685 reply.xclient.data.l[1] = 1; // accept with no rectangle
11686 if (_sapp.x11.xdnd.version >= 2) {
11687 reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy;
11688 }
11689 }
11690 XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply);
11691 XFlush(_sapp.x11.display);
11692 }
11693 break;
11694 case SelectionNotify:
11695 if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) {
11696 char* data = 0;
11697 uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor,
11698 event->xselection.property,
11699 event->xselection.target,
11700 (unsigned char**) &data);
11701 if (_sapp.drop.enabled && result) {
11702 if (_sapp_x11_parse_dropped_files_list(data)) {
11703 if (_sapp_events_enabled()) {
11704 _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED);
11705 _sapp_call_event(&_sapp.event);
11706 }
11707 }
11708 }
11709 if (_sapp.x11.xdnd.version >= 2) {
11710 XEvent reply;
11711 _sapp_clear(&reply, sizeof(reply));
11712 reply.type = ClientMessage;
11713 reply.xclient.window = _sapp.x11.window;
11714 reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished;
11715 reply.xclient.format = 32;
11716 reply.xclient.data.l[0] = (long)_sapp.x11.window;
11717 reply.xclient.data.l[1] = result;
11718 reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy;
11719 XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply);
11720 XFlush(_sapp.x11.display);
11721 }
11722 }
11723 break;
11724 case DestroyNotify:
11725 break;
11726 }
11727}
11728
11729#if !defined(_SAPP_GLX)
11730
11731_SOKOL_PRIVATE void _sapp_egl_init(void) {
11732#if defined(SOKOL_GLCORE33)
11733 if (!eglBindAPI(EGL_OPENGL_API)) {
11734 _sapp_fail("EGL: failed to bind API");
11735 }
11736#else
11737 if (!eglBindAPI(EGL_OPENGL_ES_API)) {
11738 _sapp_fail("EGL: failed to bind API");
11739 }
11740#endif
11741
11742 _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display);
11743 if (EGL_NO_DISPLAY == _sapp.egl.display) {
11744 _sapp_fail("EGL: failed to get display");
11745 }
11746
11747 EGLint major, minor;
11748 if (!eglInitialize(_sapp.egl.display, &major, &minor)) {
11749 _sapp_fail("EGL: failed to initialize");
11750 }
11751
11752 EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0;
11753 EGLint alpha_size = _sapp.desc.alpha ? 8 : 0;
11754 const EGLint config_attrs[] = {
11755 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
11756 #if defined(SOKOL_GLCORE33)
11757 EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
11758 #elif defined(SOKOL_GLES3)
11759 EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES3_BIT,
11760 #else
11761 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
11762 #endif
11763 EGL_RED_SIZE, 8,
11764 EGL_GREEN_SIZE, 8,
11765 EGL_BLUE_SIZE, 8,
11766 EGL_ALPHA_SIZE, alpha_size,
11767 EGL_DEPTH_SIZE, 24,
11768 EGL_STENCIL_SIZE, 8,
11769 EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0,
11770 EGL_SAMPLES, sample_count,
11771 EGL_NONE,
11772 };
11773
11774 EGLConfig egl_configs[32];
11775 EGLint config_count;
11776 if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) {
11777 _sapp_fail("EGL: no available configs");
11778 }
11779
11780 EGLConfig config = egl_configs[0];
11781 for (int i = 0; i < config_count; ++i) {
11782 EGLConfig c = egl_configs[i];
11783 EGLint r, g, b, a, d, s, n;
11784 if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) &&
11785 eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) &&
11786 eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) &&
11787 eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) &&
11788 eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) &&
11789 eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) &&
11790 eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) &&
11791 (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) {
11792 config = c;
11793 break;
11794 }
11795 }
11796
11797 EGLint visual_id;
11798 if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) {
11799 _sapp_fail("EGL: failed to get native visual");
11800 }
11801
11802 XVisualInfo visual_info_template;
11803 _sapp_clear(&visual_info_template, sizeof(visual_info_template));
11804 visual_info_template.visualid = (VisualID)visual_id;
11805
11806 int num_visuals;
11807 XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals);
11808 if (!visual_info) {
11809 _sapp_fail("EGL: failed to get x11 visual");
11810 }
11811
11812 _sapp_x11_create_window(visual_info->visual, visual_info->depth);
11813 XFree(visual_info);
11814
11815 _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL);
11816 if (EGL_NO_SURFACE == _sapp.egl.surface) {
11817 _sapp_fail("EGL: failed to create EGL surface");
11818 }
11819
11820 EGLint ctx_attrs[] = {
11821 #if defined(SOKOL_GLCORE33)
11822 EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version,
11823 EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version,
11824 EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
11825 #elif defined(SOKOL_GLES3)
11826 EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3,
11827 #else
11828 EGL_CONTEXT_CLIENT_VERSION, 2,
11829 #endif
11830 EGL_NONE,
11831 };
11832
11833 _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs);
11834 if (EGL_NO_CONTEXT == _sapp.egl.context) {
11835 _sapp_fail("EGL: failed to create GL context");
11836 }
11837
11838 if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) {
11839 _sapp_fail("EGL: failed to set current context");
11840 }
11841
11842 eglSwapInterval(_sapp.egl.display, _sapp.swap_interval);
11843
11844#if defined(SOKOL_GLES3)
11845 _sapp.gles2_fallback = _sapp.desc.gl_force_gles2;
11846#endif
11847}
11848
11849_SOKOL_PRIVATE void _sapp_egl_destroy(void) {
11850 if (_sapp.egl.display != EGL_NO_DISPLAY) {
11851 eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
11852
11853 if (_sapp.egl.context != EGL_NO_CONTEXT) {
11854 eglDestroyContext(_sapp.egl.display, _sapp.egl.context);
11855 _sapp.egl.context = EGL_NO_CONTEXT;
11856 }
11857
11858 if (_sapp.egl.surface != EGL_NO_SURFACE) {
11859 eglDestroySurface(_sapp.egl.display, _sapp.egl.surface);
11860 _sapp.egl.surface = EGL_NO_SURFACE;
11861 }
11862
11863 eglTerminate(_sapp.egl.display);
11864 _sapp.egl.display = EGL_NO_DISPLAY;
11865 }
11866}
11867
11868#endif /* _SAPP_GLX */
11869
11870_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) {
11871 /* The following lines are here to trigger a linker error instead of an
11872 obscure runtime error if the user has forgotten to add -pthread to
11873 the compiler or linker options. They have no other purpose.
11874 */
11875 pthread_attr_t pthread_attr;
11876 pthread_attr_init(&pthread_attr);
11877 pthread_attr_destroy(&pthread_attr);
11878
11879 _sapp_init_state(desc);
11880 _sapp.x11.window_state = NormalState;
11881
11882 XInitThreads();
11883 XrmInitialize();
11884 _sapp.x11.display = XOpenDisplay(NULL);
11885 if (!_sapp.x11.display) {
11886 _sapp_fail("XOpenDisplay() failed!\n");
11887 }
11888 _sapp.x11.screen = DefaultScreen(_sapp.x11.display);
11889 _sapp.x11.root = DefaultRootWindow(_sapp.x11.display);
11890 XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL);
11891 _sapp_x11_query_system_dpi();
11892 _sapp.dpi_scale = _sapp.x11.dpi / 96.0f;
11893 _sapp_x11_init_extensions();
11894 _sapp_x11_create_cursors();
11895#if defined(_SAPP_GLX)
11896 _sapp_glx_init();
11897 Visual* visual = 0;
11898 int depth = 0;
11899 _sapp_glx_choose_visual(&visual, &depth);
11900 _sapp_x11_create_window(visual, depth);
11901 _sapp_glx_create_context();
11902 _sapp_glx_swapinterval(_sapp.swap_interval);
11903#else
11904 _sapp_egl_init();
11905#endif
11906 sapp_set_icon(&desc->icon);
11907 _sapp.valid = true;
11908 _sapp_x11_show_window();
11909 if (_sapp.fullscreen) {
11910 _sapp_x11_set_fullscreen(true);
11911 }
11912
11913 XFlush(_sapp.x11.display);
11914 while (!_sapp.quit_ordered) {
11915 _sapp_timing_measure(&_sapp.timing);
11916 int count = XPending(_sapp.x11.display);
11917 while (count--) {
11918 XEvent event;
11919 XNextEvent(_sapp.x11.display, &event);
11920 _sapp_x11_process_event(&event);
11921 }
11922 _sapp_frame();
11923#if defined(_SAPP_GLX)
11924 _sapp_glx_swap_buffers();
11925#else
11926 eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface);
11927#endif
11928 XFlush(_sapp.x11.display);
11929 /* handle quit-requested, either from window or from sapp_request_quit() */
11930 if (_sapp.quit_requested && !_sapp.quit_ordered) {
11931 /* give user code a chance to intervene */
11932 _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED);
11933 /* if user code hasn't intervened, quit the app */
11934 if (_sapp.quit_requested) {
11935 _sapp.quit_ordered = true;
11936 }
11937 }
11938 }
11939 _sapp_call_cleanup();
11940#if defined(_SAPP_GLX)
11941 _sapp_glx_destroy_context();
11942#else
11943 _sapp_egl_destroy();
11944#endif
11945 _sapp_x11_destroy_window();
11946 _sapp_x11_destroy_cursors();
11947 XCloseDisplay(_sapp.x11.display);
11948 _sapp_discard_state();
11949}
11950
11951#if !defined(SOKOL_NO_ENTRY)
11952int main(int argc, char* argv[]) {
11953 sapp_desc desc = sokol_main(argc, argv);
11954 _sapp_linux_run(&desc);
11955 return 0;
11956}
11957#endif /* SOKOL_NO_ENTRY */
11958#endif /* _SAPP_LINUX */
11959
11960/*== PUBLIC API FUNCTIONS ====================================================*/
11961#if defined(SOKOL_NO_ENTRY)
11962SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) {
11963 SOKOL_ASSERT(desc);
11964 #if defined(_SAPP_MACOS)
11965 _sapp_macos_run(desc);
11966 #elif defined(_SAPP_IOS)
11967 _sapp_ios_run(desc);
11968 #elif defined(_SAPP_EMSCRIPTEN)
11969 _sapp_emsc_run(desc);
11970 #elif defined(_SAPP_WIN32)
11971 _sapp_win32_run(desc);
11972 #elif defined(_SAPP_UWP)
11973 _sapp_uwp_run(desc);
11974 #elif defined(_SAPP_LINUX)
11975 _sapp_linux_run(desc);
11976 #else
11977 // calling sapp_run() directly is not supported on Android)
11978 _sapp_fail("sapp_run() not supported on this platform!");
11979 #endif
11980}
11981
11982/* this is just a stub so the linker doesn't complain */
11983sapp_desc sokol_main(int argc, char* argv[]) {
11984 _SOKOL_UNUSED(argc);
11985 _SOKOL_UNUSED(argv);
11986 sapp_desc desc;
11987 _sapp_clear(&desc, sizeof(desc));
11988 return desc;
11989}
11990#else
11991/* likewise, in normal mode, sapp_run() is just an empty stub */
11992SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) {
11993 _SOKOL_UNUSED(desc);
11994}
11995#endif
11996
11997SOKOL_API_IMPL bool sapp_isvalid(void) {
11998 return _sapp.valid;
11999}
12000
12001SOKOL_API_IMPL void* sapp_userdata(void) {
12002 return _sapp.desc.user_data;
12003}
12004
12005SOKOL_API_IMPL sapp_desc sapp_query_desc(void) {
12006 return _sapp.desc;
12007}
12008
12009SOKOL_API_IMPL uint64_t sapp_frame_count(void) {
12010 return _sapp.frame_count;
12011}
12012
12013SOKOL_API_IMPL double sapp_frame_duration(void) {
12014 return _sapp_timing_get_avg(&_sapp.timing);
12015}
12016
12017SOKOL_API_IMPL int sapp_width(void) {
12018 return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1;
12019}
12020
12021SOKOL_API_IMPL float sapp_widthf(void) {
12022 return (float)sapp_width();
12023}
12024
12025SOKOL_API_IMPL int sapp_height(void) {
12026 return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1;
12027}
12028
12029SOKOL_API_IMPL float sapp_heightf(void) {
12030 return (float)sapp_height();
12031}
12032
12033SOKOL_API_IMPL int sapp_color_format(void) {
12034 #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU)
12035 switch (_sapp.emsc.wgpu.render_format) {
12036 case WGPUTextureFormat_RGBA8Unorm:
12037 return _SAPP_PIXELFORMAT_RGBA8;
12038 case WGPUTextureFormat_BGRA8Unorm:
12039 return _SAPP_PIXELFORMAT_BGRA8;
12040 default:
12041 SOKOL_UNREACHABLE;
12042 return 0;
12043 }
12044 #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11)
12045 return _SAPP_PIXELFORMAT_BGRA8;
12046 #else
12047 return _SAPP_PIXELFORMAT_RGBA8;
12048 #endif
12049}
12050
12051SOKOL_API_IMPL int sapp_depth_format(void) {
12052 return _SAPP_PIXELFORMAT_DEPTH_STENCIL;
12053}
12054
12055SOKOL_API_IMPL int sapp_sample_count(void) {
12056 return _sapp.sample_count;
12057}
12058
12059SOKOL_API_IMPL bool sapp_high_dpi(void) {
12060 return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f);
12061}
12062
12063SOKOL_API_IMPL float sapp_dpi_scale(void) {
12064 return _sapp.dpi_scale;
12065}
12066
12067SOKOL_APP_IMPL const void* sapp_egl_get_display(void) {
12068 SOKOL_ASSERT(_sapp.valid);
12069 #if defined(_SAPP_ANDROID)
12070 return _sapp.android.display;
12071 #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX)
12072 return _sapp.egl.display;
12073 #else
12074 return 0;
12075 #endif
12076}
12077
12078SOKOL_APP_IMPL const void* sapp_egl_get_context(void) {
12079 SOKOL_ASSERT(_sapp.valid);
12080 #if defined(_SAPP_ANDROID)
12081 return _sapp.android.context;
12082 #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX)
12083 return _sapp.egl.context;
12084 #else
12085 return 0;
12086 #endif
12087}
12088
12089SOKOL_API_IMPL bool sapp_gles2(void) {
12090 return _sapp.gles2_fallback;
12091}
12092
12093SOKOL_API_IMPL void sapp_show_keyboard(bool show) {
12094 #if defined(_SAPP_IOS)
12095 _sapp_ios_show_keyboard(show);
12096 #elif defined(_SAPP_EMSCRIPTEN)
12097 _sapp_emsc_show_keyboard(show);
12098 #elif defined(_SAPP_ANDROID)
12099 _sapp_android_show_keyboard(show);
12100 #else
12101 _SOKOL_UNUSED(show);
12102 #endif
12103}
12104
12105SOKOL_API_IMPL bool sapp_keyboard_shown(void) {
12106 return _sapp.onscreen_keyboard_shown;
12107}
12108
12109SOKOL_API_IMPL bool sapp_is_fullscreen(void) {
12110 return _sapp.fullscreen;
12111}
12112
12113SOKOL_API_IMPL void sapp_toggle_fullscreen(void) {
12114 #if defined(_SAPP_MACOS)
12115 _sapp_macos_toggle_fullscreen();
12116 #elif defined(_SAPP_WIN32)
12117 _sapp_win32_toggle_fullscreen();
12118 #elif defined(_SAPP_UWP)
12119 _sapp_uwp_toggle_fullscreen();
12120 #elif defined(_SAPP_LINUX)
12121 _sapp_x11_toggle_fullscreen();
12122 #endif
12123}
12124
12125/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */
12126SOKOL_API_IMPL void sapp_show_mouse(bool show) {
12127 if (_sapp.mouse.shown != show) {
12128 #if defined(_SAPP_MACOS)
12129 _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show);
12130 #elif defined(_SAPP_WIN32)
12131 _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false);
12132 #elif defined(_SAPP_LINUX)
12133 _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show);
12134 #elif defined(_SAPP_UWP)
12135 _sapp_uwp_update_cursor(_sapp.mouse.current_cursor, show);
12136 #elif defined(_SAPP_EMSCRIPTEN)
12137 _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show);
12138 #endif
12139 _sapp.mouse.shown = show;
12140 }
12141}
12142
12143SOKOL_API_IMPL bool sapp_mouse_shown(void) {
12144 return _sapp.mouse.shown;
12145}
12146
12147SOKOL_API_IMPL void sapp_lock_mouse(bool lock) {
12148 #if defined(_SAPP_MACOS)
12149 _sapp_macos_lock_mouse(lock);
12150 #elif defined(_SAPP_EMSCRIPTEN)
12151 _sapp_emsc_lock_mouse(lock);
12152 #elif defined(_SAPP_WIN32)
12153 _sapp_win32_lock_mouse(lock);
12154 #elif defined(_SAPP_LINUX)
12155 _sapp_x11_lock_mouse(lock);
12156 #else
12157 _sapp.mouse.locked = lock;
12158 #endif
12159}
12160
12161SOKOL_API_IMPL bool sapp_mouse_locked(void) {
12162 return _sapp.mouse.locked;
12163}
12164
12165SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) {
12166 SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
12167 if (_sapp.mouse.current_cursor != cursor) {
12168 #if defined(_SAPP_MACOS)
12169 _sapp_macos_update_cursor(cursor, _sapp.mouse.shown);
12170 #elif defined(_SAPP_WIN32)
12171 _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false);
12172 #elif defined(_SAPP_LINUX)
12173 _sapp_x11_update_cursor(cursor, _sapp.mouse.shown);
12174 #elif defined(_SAPP_UWP)
12175 _sapp_uwp_update_cursor(cursor, _sapp.mouse.shown);
12176 #elif defined(_SAPP_EMSCRIPTEN)
12177 _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown);
12178 #endif
12179 _sapp.mouse.current_cursor = cursor;
12180 }
12181}
12182
12183SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) {
12184 return _sapp.mouse.current_cursor;
12185}
12186
12187SOKOL_API_IMPL void sapp_request_quit(void) {
12188 _sapp.quit_requested = true;
12189}
12190
12191SOKOL_API_IMPL void sapp_cancel_quit(void) {
12192 _sapp.quit_requested = false;
12193}
12194
12195SOKOL_API_IMPL void sapp_quit(void) {
12196 _sapp.quit_ordered = true;
12197}
12198
12199SOKOL_API_IMPL void sapp_consume_event(void) {
12200 _sapp.event_consumed = true;
12201}
12202
12203/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */
12204SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) {
12205 if (!_sapp.clipboard.enabled) {
12206 return;
12207 }
12208 SOKOL_ASSERT(str);
12209 #if defined(_SAPP_MACOS)
12210 _sapp_macos_set_clipboard_string(str);
12211 #elif defined(_SAPP_EMSCRIPTEN)
12212 _sapp_emsc_set_clipboard_string(str);
12213 #elif defined(_SAPP_WIN32)
12214 _sapp_win32_set_clipboard_string(str);
12215 #else
12216 /* not implemented */
12217 #endif
12218 _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size);
12219}
12220
12221SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) {
12222 if (!_sapp.clipboard.enabled) {
12223 return "";
12224 }
12225 #if defined(_SAPP_MACOS)
12226 return _sapp_macos_get_clipboard_string();
12227 #elif defined(_SAPP_EMSCRIPTEN)
12228 return _sapp.clipboard.buffer;
12229 #elif defined(_SAPP_WIN32)
12230 return _sapp_win32_get_clipboard_string();
12231 #else
12232 /* not implemented */
12233 return _sapp.clipboard.buffer;
12234 #endif
12235}
12236
12237SOKOL_API_IMPL void sapp_set_window_title(const char* title) {
12238 SOKOL_ASSERT(title);
12239 _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title));
12240 #if defined(_SAPP_MACOS)
12241 _sapp_macos_update_window_title();
12242 #elif defined(_SAPP_WIN32)
12243 _sapp_win32_update_window_title();
12244 #elif defined(_SAPP_LINUX)
12245 _sapp_x11_update_window_title();
12246 #endif
12247}
12248
12249SOKOL_API_IMPL void sapp_set_icon(const sapp_icon_desc* desc) {
12250 SOKOL_ASSERT(desc);
12251 if (desc->sokol_default) {
12252 if (0 == _sapp.default_icon_pixels) {
12253 _sapp_setup_default_icon();
12254 }
12255 SOKOL_ASSERT(0 != _sapp.default_icon_pixels);
12256 desc = &_sapp.default_icon_desc;
12257 }
12258 const int num_images = _sapp_icon_num_images(desc);
12259 if (num_images == 0) {
12260 return;
12261 }
12262 SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES));
12263 if (!_sapp_validate_icon_desc(desc, num_images)) {
12264 return;
12265 }
12266 #if defined(_SAPP_MACOS)
12267 _sapp_macos_set_icon(desc, num_images);
12268 #elif defined(_SAPP_WIN32)
12269 _sapp_win32_set_icon(desc, num_images);
12270 #elif defined(_SAPP_LINUX)
12271 _sapp_x11_set_icon(desc, num_images);
12272 #elif defined(_SAPP_EMSCRIPTEN)
12273 _sapp_emsc_set_icon(desc, num_images);
12274 #endif
12275}
12276
12277SOKOL_API_IMPL int sapp_get_num_dropped_files(void) {
12278 SOKOL_ASSERT(_sapp.drop.enabled);
12279 return _sapp.drop.num_files;
12280}
12281
12282SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) {
12283 SOKOL_ASSERT(_sapp.drop.enabled);
12284 SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files));
12285 SOKOL_ASSERT(_sapp.drop.buffer);
12286 if (!_sapp.drop.enabled) {
12287 return "";
12288 }
12289 if ((index < 0) || (index >= _sapp.drop.max_files)) {
12290 return "";
12291 }
12292 return (const char*) _sapp_dropped_file_path_ptr(index);
12293}
12294
12295SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) {
12296 SOKOL_ASSERT(_sapp.drop.enabled);
12297 SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files));
12298 #if defined(_SAPP_EMSCRIPTEN)
12299 if (!_sapp.drop.enabled) {
12300 return 0;
12301 }
12302 return sapp_js_dropped_file_size(index);
12303 #else
12304 (void)index;
12305 return 0;
12306 #endif
12307}
12308
12309SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) {
12310 SOKOL_ASSERT(_sapp.drop.enabled);
12311 SOKOL_ASSERT(request);
12312 SOKOL_ASSERT(request->callback);
12313 SOKOL_ASSERT(request->buffer.ptr);
12314 SOKOL_ASSERT(request->buffer.size > 0);
12315 #if defined(_SAPP_EMSCRIPTEN)
12316 const int index = request->dropped_file_index;
12317 sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR;
12318 if ((index < 0) || (index >= _sapp.drop.num_files)) {
12319 error_code = SAPP_HTML5_FETCH_ERROR_OTHER;
12320 }
12321 if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) {
12322 error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL;
12323 }
12324 if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) {
12325 _sapp_emsc_invoke_fetch_cb(index,
12326 false, // success
12327 (int)error_code,
12328 request->callback,
12329 0, // fetched_size
12330 (void*)request->buffer.ptr,
12331 request->buffer.size,
12332 request->user_data);
12333 }
12334 else {
12335 sapp_js_fetch_dropped_file(index,
12336 request->callback,
12337 (void*)request->buffer.ptr,
12338 request->buffer.size,
12339 request->user_data);
12340 }
12341 #else
12342 (void)request;
12343 #endif
12344}
12345
12346SOKOL_API_IMPL const void* sapp_metal_get_device(void) {
12347 SOKOL_ASSERT(_sapp.valid);
12348 #if defined(SOKOL_METAL)
12349 #if defined(_SAPP_MACOS)
12350 const void* obj = (__bridge const void*) _sapp.macos.mtl_device;
12351 #else
12352 const void* obj = (__bridge const void*) _sapp.ios.mtl_device;
12353 #endif
12354 SOKOL_ASSERT(obj);
12355 return obj;
12356 #else
12357 return 0;
12358 #endif
12359}
12360
12361SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) {
12362 SOKOL_ASSERT(_sapp.valid);
12363 #if defined(SOKOL_METAL)
12364 #if defined(_SAPP_MACOS)
12365 const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor];
12366 #else
12367 const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor];
12368 #endif
12369 SOKOL_ASSERT(obj);
12370 return obj;
12371 #else
12372 return 0;
12373 #endif
12374}
12375
12376SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) {
12377 SOKOL_ASSERT(_sapp.valid);
12378 #if defined(SOKOL_METAL)
12379 #if defined(_SAPP_MACOS)
12380 const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable];
12381 #else
12382 const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable];
12383 #endif
12384 SOKOL_ASSERT(obj);
12385 return obj;
12386 #else
12387 return 0;
12388 #endif
12389}
12390
12391SOKOL_API_IMPL const void* sapp_macos_get_window(void) {
12392 #if defined(_SAPP_MACOS)
12393 const void* obj = (__bridge const void*) _sapp.macos.window;
12394 SOKOL_ASSERT(obj);
12395 return obj;
12396 #else
12397 return 0;
12398 #endif
12399}
12400
12401SOKOL_API_IMPL const void* sapp_ios_get_window(void) {
12402 #if defined(_SAPP_IOS)
12403 const void* obj = (__bridge const void*) _sapp.ios.window;
12404 SOKOL_ASSERT(obj);
12405 return obj;
12406 #else
12407 return 0;
12408 #endif
12409}
12410
12411SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) {
12412 SOKOL_ASSERT(_sapp.valid);
12413 #if defined(SOKOL_D3D11)
12414 return _sapp.d3d11.device;
12415 #else
12416 return 0;
12417 #endif
12418}
12419
12420SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) {
12421 SOKOL_ASSERT(_sapp.valid);
12422 #if defined(SOKOL_D3D11)
12423 return _sapp.d3d11.device_context;
12424 #else
12425 return 0;
12426 #endif
12427}
12428
12429SOKOL_API_IMPL const void* sapp_d3d11_get_swap_chain(void) {
12430 SOKOL_ASSERT(_sapp.valid);
12431#if defined(SOKOL_D3D11)
12432 return _sapp.d3d11.swap_chain;
12433#else
12434 return 0;
12435#endif
12436}
12437
12438SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) {
12439 SOKOL_ASSERT(_sapp.valid);
12440 #if defined(SOKOL_D3D11)
12441 if (_sapp.d3d11.msaa_rtv) {
12442 return _sapp.d3d11.msaa_rtv;
12443 }
12444 else {
12445 return _sapp.d3d11.rtv;
12446 }
12447 #else
12448 return 0;
12449 #endif
12450}
12451
12452SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) {
12453 SOKOL_ASSERT(_sapp.valid);
12454 #if defined(SOKOL_D3D11)
12455 return _sapp.d3d11.dsv;
12456 #else
12457 return 0;
12458 #endif
12459}
12460
12461SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) {
12462 SOKOL_ASSERT(_sapp.valid);
12463 #if defined(_SAPP_WIN32)
12464 return _sapp.win32.hwnd;
12465 #else
12466 return 0;
12467 #endif
12468}
12469
12470SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) {
12471 SOKOL_ASSERT(_sapp.valid);
12472 #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU)
12473 return (const void*) _sapp.emsc.wgpu.device;
12474 #else
12475 return 0;
12476 #endif
12477}
12478
12479SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) {
12480 SOKOL_ASSERT(_sapp.valid);
12481 #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU)
12482 if (_sapp.sample_count > 1) {
12483 return (const void*) _sapp.emsc.wgpu.msaa_view;
12484 }
12485 else {
12486 return (const void*) _sapp.emsc.wgpu.swapchain_view;
12487 }
12488 #else
12489 return 0;
12490 #endif
12491}
12492
12493SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) {
12494 SOKOL_ASSERT(_sapp.valid);
12495 #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU)
12496 if (_sapp.sample_count > 1) {
12497 return (const void*) _sapp.emsc.wgpu.swapchain_view;
12498 }
12499 else {
12500 return 0;
12501 }
12502 #else
12503 return 0;
12504 #endif
12505}
12506
12507SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) {
12508 SOKOL_ASSERT(_sapp.valid);
12509 #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU)
12510 return (const void*) _sapp.emsc.wgpu.depth_stencil_view;
12511 #else
12512 return 0;
12513 #endif
12514}
12515
12516SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) {
12517 // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity()
12518 // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708)
12519 #if defined(_SAPP_ANDROID)
12520 return (void*)_sapp.android.activity;
12521 #else
12522 return 0;
12523 #endif
12524}
12525
12526SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) {
12527 _sapp.html5_ask_leave_site = ask;
12528}
12529
12530#endif /* SOKOL_APP_IMPL */