GCC Code Coverage Report


Directory: ./
File: submodules/raylib/src/rcore.c
Date: 2023-09-29 04:53:15
Exec Total Coverage
Lines: 0 1337 0.0%
Branches: 0 766 0.0%

Line Branch Exec Source
1 /**********************************************************************************************
2 *
3 * rcore - Basic functions to manage windows, OpenGL context and input on multiple platforms
4 *
5 * PLATFORMS SUPPORTED:
6 * - PLATFORM_DESKTOP: Windows (Win32, Win64)
7 * - PLATFORM_DESKTOP: Linux (X11 desktop mode)
8 * - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop)
9 * - PLATFORM_DESKTOP: OSX/macOS
10 * - PLATFORM_ANDROID: Android (ARM, ARM64)
11 * - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian, native mode)
12 * - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver
13 * - PLATFORM_WEB: HTML5 with WebAssembly
14 *
15 * CONFIGURATION:
16 * #define PLATFORM_DESKTOP
17 * Windowing and input system configured for desktop platforms:
18 * Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly
19 *
20 * #define PLATFORM_ANDROID
21 * Windowing and input system configured for Android device, app activity managed internally in this module.
22 * NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL
23 *
24 * #define PLATFORM_RPI (deprecated - RPI OS Buster only)
25 * Windowing and input system configured for Raspberry Pi in native mode (no XWindow required),
26 * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/
27 * WARNING: This platform is deprecated, since RPI OS Bullseye, the old Dispmanx libraries are not
28 * supported and you must be using PLATFORM_DRM
29 *
30 * #define PLATFORM_DRM
31 * Windowing and input system configured for DRM native mode (RPI4 and other devices)
32 * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/
33 *
34 * #define PLATFORM_WEB
35 * Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js
36 * using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code.
37 *
38 * #define SUPPORT_DEFAULT_FONT (default)
39 * Default font is loaded on window initialization to be available for the user to render simple text.
40 * NOTE: If enabled, uses external module functions to load default raylib font (module: text)
41 *
42 * #define SUPPORT_CAMERA_SYSTEM
43 * Camera module is included (rcamera.h) and multiple predefined cameras are available:
44 * free, 1st/3rd person, orbital, custom
45 *
46 * #define SUPPORT_GESTURES_SYSTEM
47 * Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag
48 *
49 * #define SUPPORT_MOUSE_GESTURES
50 * Mouse gestures are directly mapped like touches and processed by gestures system.
51 *
52 * #define SUPPORT_TOUCH_AS_MOUSE
53 * Touch input and mouse input are shared. Mouse functions also return touch information.
54 *
55 * #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only)
56 * Reconfigure standard input to receive key inputs, works with SSH connection.
57 * WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other
58 * running processes orblocking the device if not restored properly. Use with care.
59 *
60 * #define SUPPORT_BUSY_WAIT_LOOP
61 * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used
62 *
63 * #define SUPPORT_PARTIALBUSY_WAIT_LOOP
64 * Use a partial-busy wait loop, in this case frame sleeps for most of the time and runs a busy-wait-loop at the end
65 *
66 * #define SUPPORT_EVENTS_WAITING
67 * Wait for events passively (sleeping while no events) instead of polling them actively every frame
68 *
69 * #define SUPPORT_SCREEN_CAPTURE
70 * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback()
71 *
72 * #define SUPPORT_GIF_RECORDING
73 * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback()
74 *
75 * #define SUPPORT_COMPRESSION_API
76 * Support CompressData() and DecompressData() functions, those functions use zlib implementation
77 * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module
78 * for linkage
79 *
80 * #define SUPPORT_EVENTS_AUTOMATION
81 * Support automatic generated events, loading and recording of those events when required
82 *
83 * DEPENDENCIES:
84 * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX, FreeBSD...)
85 * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion)
86 * camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person)
87 * gestures - Gestures system for touch-ready devices (or simulated from mouse inputs)
88 *
89 *
90 * LICENSE: zlib/libpng
91 *
92 * Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
93 *
94 * This software is provided "as-is", without any express or implied warranty. In no event
95 * will the authors be held liable for any damages arising from the use of this software.
96 *
97 * Permission is granted to anyone to use this software for any purpose, including commercial
98 * applications, and to alter it and redistribute it freely, subject to the following restrictions:
99 *
100 * 1. The origin of this software must not be misrepresented; you must not claim that you
101 * wrote the original software. If you use this software in a product, an acknowledgment
102 * in the product documentation would be appreciated but is not required.
103 *
104 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented
105 * as being the original software.
106 *
107 * 3. This notice may not be removed or altered from any source distribution.
108 *
109 **********************************************************************************************/
110
111 #include "raylib.h" // Declares module functions
112
113 // Check if config flags have been externally provided on compilation line
114 #if !defined(EXTERNAL_CONFIG_FLAGS)
115 #include "config.h" // Defines module configuration flags
116 #endif
117
118 #include "utils.h" // Required for: TRACELOG() macros
119
120 #define RLGL_IMPLEMENTATION
121 #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2
122
123 #define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation
124 #include "raymath.h" // Vector3, Quaternion and Matrix functionality
125
126 #if defined(SUPPORT_GESTURES_SYSTEM)
127 #define RGESTURES_IMPLEMENTATION
128 #include "rgestures.h" // Gestures detection functionality
129 #endif
130
131 #if defined(SUPPORT_CAMERA_SYSTEM)
132 #define RCAMERA_IMPLEMENTATION
133 #include "rcamera.h" // Camera system functionality
134 #endif
135
136 #if defined(SUPPORT_GIF_RECORDING)
137 #define MSF_GIF_MALLOC(contextPointer, newSize) RL_MALLOC(newSize)
138 #define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) RL_REALLOC(oldMemory, newSize)
139 #define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) RL_FREE(oldMemory)
140
141 #define MSF_GIF_IMPL
142 #include "external/msf_gif.h" // GIF recording functionality
143 #endif
144
145 #if defined(SUPPORT_COMPRESSION_API)
146 #define SINFL_IMPLEMENTATION
147 #define SINFL_NO_SIMD
148 #include "external/sinfl.h" // Deflate (RFC 1951) decompressor
149
150 #define SDEFL_IMPLEMENTATION
151 #include "external/sdefl.h" // Deflate (RFC 1951) compressor
152 #endif
153
154 #if (defined(__linux__) || defined(PLATFORM_WEB)) && (_POSIX_C_SOURCE < 199309L)
155 #undef _POSIX_C_SOURCE
156 #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext.
157 #endif
158 #if defined(__linux__) && !defined(_GNU_SOURCE)
159 #define _GNU_SOURCE
160 #endif
161
162 // Platform specific defines to handle GetApplicationDirectory()
163 #if defined (PLATFORM_DESKTOP)
164 #if defined(_WIN32)
165 #ifndef MAX_PATH
166 #define MAX_PATH 1025
167 #endif
168 __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void *hModule, void *lpFilename, unsigned long nSize);
169 __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(void *hModule, void *lpFilename, unsigned long nSize);
170 __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, void *widestr, int cchwide, void *str, int cbmb, void *defchar, int *used_default);
171 #elif defined(__linux__)
172 #include <unistd.h>
173 #elif defined(__APPLE__)
174 #include <sys/syslimits.h>
175 #include <mach-o/dyld.h>
176 #endif // OSs
177 #endif // PLATFORM_DESKTOP
178
179 #include <stdlib.h> // Required for: srand(), rand(), atexit()
180 #include <stdio.h> // Required for: sprintf() [Used in OpenURL()]
181 #include <string.h> // Required for: strrchr(), strcmp(), strlen(), memset()
182 #include <time.h> // Required for: time() [Used in InitTimer()]
183 #include <math.h> // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()]
184
185 #define _CRT_INTERNAL_NONSTDC_NAMES 1
186 #include <sys/stat.h> // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()]
187
188 #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
189 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
190 #endif
191
192 #if defined(PLATFORM_DESKTOP) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
193 #define DIRENT_MALLOC RL_MALLOC
194 #define DIRENT_FREE RL_FREE
195
196 #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()]
197 #else
198 #include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()]
199 #endif
200
201 #if defined(_WIN32)
202 #include <direct.h> // Required for: _getch(), _chdir()
203 #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir()
204 #define CHDIR _chdir
205 #include <io.h> // Required for: _access() [Used in FileExists()]
206 #else
207 #include <unistd.h> // Required for: getch(), chdir() (POSIX), access()
208 #define GETCWD getcwd
209 #define CHDIR chdir
210 #endif
211
212 #if defined(PLATFORM_DESKTOP)
213 #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3
214 // NOTE: Already provided by rlgl implementation (on glad.h)
215 #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management
216 // NOTE: GLFW3 already includes gl.h (OpenGL) headers
217
218 // Support retrieving native window handlers
219 #if defined(_WIN32)
220 typedef void *PVOID;
221 typedef PVOID HANDLE;
222 typedef HANDLE HWND;
223 #define GLFW_EXPOSE_NATIVE_WIN32
224 #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h
225 #include "GLFW/glfw3native.h"
226
227 #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP)
228 // NOTE: Those functions require linking with winmm library
229 unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod);
230 unsigned int __stdcall timeEndPeriod(unsigned int uPeriod);
231 #endif
232 #endif
233 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
234 #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
235
236 //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type
237 //#define GLFW_EXPOSE_NATIVE_WAYLAND
238 //#define GLFW_EXPOSE_NATIVE_MIR
239 #include "GLFW/glfw3native.h" // Required for: glfwGetX11Window()
240 #endif
241 #if defined(__APPLE__)
242 #include <unistd.h> // Required for: usleep()
243
244 //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition
245 void *glfwGetCocoaWindow(GLFWwindow* handle);
246 #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow()
247 #endif
248
249 // TODO: HACK: Added flag if not provided by GLFW when using external library
250 // Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev
251 #if !defined(GLFW_MOUSE_PASSTHROUGH)
252 #define GLFW_MOUSE_PASSTHROUGH 0x0002000D
253 #endif
254 #endif
255
256 #if defined(PLATFORM_ANDROID)
257 //#include <android/sensor.h> // Required for: Android sensors functions (accelerometer, gyroscope, light...)
258 #include <android/window.h> // Required for: AWINDOW_FLAG_FULLSCREEN definition and others
259 #include <android_native_app_glue.h> // Required for: android_app struct and activity management
260 #include <jni.h> // Required for: JNIEnv and JavaVM [Used in OpenURL()]
261
262 #include <EGL/egl.h> // Native platform windowing system interface
263 //#include <GLES2/gl2.h> // OpenGL ES 2.0 library (not required in this module, only in rlgl)
264 #endif
265
266 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
267 #include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl()
268 #include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO
269 #include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr()
270 #include <pthread.h> // POSIX threads management (inputs reading)
271 #include <dirent.h> // POSIX directory browsing
272
273 #include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations
274 #include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition
275 #include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...)
276 #include <linux/joystick.h> // Linux: Joystick support library
277
278 #if defined(PLATFORM_RPI)
279 #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions
280 #endif
281 #if defined(PLATFORM_DRM)
282 #include <gbm.h> // Generic Buffer Management (native platform for EGL on DRM)
283 #include <xf86drm.h> // Direct Rendering Manager user-level library interface
284 #include <xf86drmMode.h> // Direct Rendering Manager mode setting (KMS) interface
285 #endif
286
287 #include "EGL/egl.h" // Native platform windowing system interface
288 #include "EGL/eglext.h" // EGL extensions
289 //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module, only in rlgl)
290 #endif
291
292 #if defined(PLATFORM_WEB)
293 #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL)
294 //#define GLFW_INCLUDE_ES3 // GLFW3: Enable OpenGL ES 3.0 (transalted to WebGL2?)
295 #include "GLFW/glfw3.h" // GLFW3: Windows, OpenGL context and Input management
296 #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
297
298 #include <emscripten/emscripten.h> // Emscripten functionality for C
299 #include <emscripten/html5.h> // Emscripten HTML5 library
300 #endif
301
302 //----------------------------------------------------------------------------------
303 // Defines and Macros
304 //----------------------------------------------------------------------------------
305 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
306 #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number
307
308 #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...)
309 #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events
310 #endif
311
312 #ifndef MAX_FILEPATH_CAPACITY
313 #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath
314 #endif
315 #ifndef MAX_FILEPATH_LENGTH
316 #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value)
317 #endif
318
319 #ifndef MAX_KEYBOARD_KEYS
320 #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported
321 #endif
322 #ifndef MAX_MOUSE_BUTTONS
323 #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported
324 #endif
325 #ifndef MAX_GAMEPADS
326 #define MAX_GAMEPADS 4 // Maximum number of gamepads supported
327 #endif
328 #ifndef MAX_GAMEPAD_AXIS
329 #define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad)
330 #endif
331 #ifndef MAX_GAMEPAD_BUTTONS
332 #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad)
333 #endif
334 #ifndef MAX_TOUCH_POINTS
335 #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported
336 #endif
337 #ifndef MAX_KEY_PRESSED_QUEUE
338 #define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue
339 #endif
340 #ifndef MAX_CHAR_PRESSED_QUEUE
341 #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue
342 #endif
343
344 #ifndef MAX_DECOMPRESSION_SIZE
345 #define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB
346 #endif
347
348 // Flags operation macros
349 #define FLAG_SET(n, f) ((n) |= (f))
350 #define FLAG_CLEAR(n, f) ((n) &= ~(f))
351 #define FLAG_TOGGLE(n, f) ((n) ^= (f))
352 #define FLAG_CHECK(n, f) ((n) & (f))
353
354 //----------------------------------------------------------------------------------
355 // Types and Structures Definition
356 //----------------------------------------------------------------------------------
357 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
358 typedef struct {
359 pthread_t threadId; // Event reading thread id
360 int fd; // File descriptor to the device it is assigned to
361 int eventNum; // Number of 'event<N>' device
362 Rectangle absRange; // Range of values for absolute pointing devices (touchscreens)
363 int touchSlot; // Hold the touch slot number of the currently being sent multitouch block
364 bool isMouse; // True if device supports relative X Y movements
365 bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH
366 bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH
367 bool isKeyboard; // True if device has letter keycodes
368 bool isGamepad; // True if device has gamepad buttons
369 } InputEventWorker;
370 #endif
371
372 typedef struct { int x; int y; } Point;
373 typedef struct { unsigned int width; unsigned int height; } Size;
374
375 // Core global state context data
376 typedef struct CoreData {
377 struct {
378 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
379 GLFWwindow *handle; // GLFW window handle (graphic device)
380 #endif
381 #if defined(PLATFORM_RPI)
382 EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device)
383 #endif
384 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
385 #if defined(PLATFORM_DRM)
386 int fd; // File descriptor for /dev/dri/...
387 drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector
388 drmModeCrtc *crtc; // CRT Controller
389 int modeIndex; // Index of the used mode of connector->modes
390 struct gbm_device *gbmDevice; // GBM device
391 struct gbm_surface *gbmSurface; // GBM surface
392 struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping)
393 uint32_t prevFB; // Previous GBM framebufer (during frame swapping)
394 #endif // PLATFORM_DRM
395 EGLDisplay device; // Native display device (physical screen connection)
396 EGLSurface surface; // Surface to draw on, framebuffers (connected to context)
397 EGLContext context; // Graphic context, mode in which drawing can be done
398 EGLConfig config; // Graphic config
399 #endif
400 const char *title; // Window text title const pointer
401 unsigned int flags; // Configuration flags (bit based), keeps window state
402 bool ready; // Check if window has been initialized successfully
403 bool fullscreen; // Check if fullscreen mode is enabled
404 bool shouldClose; // Check if window set for closing
405 bool resizedLastFrame; // Check if window has been resized last frame
406 bool eventWaiting; // Wait for events before ending frame
407
408 Point position; // Window position on screen (required on fullscreen toggle)
409 Size display; // Display width and height (monitor, device-screen, LCD, ...)
410 Size screen; // Screen width and height (used render area)
411 Size currentFbo; // Current render width and height (depends on active fbo)
412 Size render; // Framebuffer width and height (render area, including black bars if required)
413 Point renderOffset; // Offset from render area (must be divided by 2)
414 Matrix screenScale; // Matrix to scale screen (framebuffer rendering)
415 Point previousPosition; // Previous screen position (required on borderless windowed toggle)
416 Size previousScreen; // Previous screen size (required on borderless windowed toggle)
417
418 char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW)
419 unsigned int dropFileCount; // Count dropped files strings
420
421 } Window;
422 #if defined(PLATFORM_ANDROID)
423 struct {
424 bool appEnabled; // Flag to detect if app is active ** = true
425 struct android_app *app; // Android activity
426 struct android_poll_source *source; // Android events polling source
427 bool contextRebindRequired; // Used to know context rebind required
428 } Android;
429 #endif
430 struct {
431 const char *basePath; // Base path for data storage
432 } Storage;
433 struct {
434 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
435 InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event<N>"
436 #endif
437 struct {
438 int exitKey; // Default exit key
439 char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state
440 char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state
441 // NOTE: Since key press logic involves comparing prev vs cur key state, we need to handle key repeats specially
442 char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame.
443
444 int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue
445 int keyPressedQueueCount; // Input keys queue count
446
447 int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode)
448 int charPressedQueueCount; // Input characters queue count
449
450 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
451 int defaultMode; // Default keyboard mode
452 #if defined(SUPPORT_SSH_KEYBOARD_RPI)
453 bool evtMode; // Keyboard in event mode
454 #endif
455 int defaultFileFlags; // Default IO file flags
456 struct termios defaultSettings; // Default keyboard settings
457 int fd; // File descriptor for the evdev keyboard
458 #endif
459 } Keyboard;
460 struct {
461 Vector2 offset; // Mouse offset
462 Vector2 scale; // Mouse scaling
463 Vector2 currentPosition; // Mouse position on screen
464 Vector2 previousPosition; // Previous mouse position
465
466 int cursor; // Tracks current mouse cursor
467 bool cursorHidden; // Track if cursor is hidden
468 bool cursorOnScreen; // Tracks if cursor is inside client area
469
470 char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state
471 char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state
472 Vector2 currentWheelMove; // Registers current mouse wheel variation
473 Vector2 previousWheelMove; // Registers previous mouse wheel variation
474 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
475 Vector2 eventWheelMove; // Registers the event mouse wheel variation
476 // NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update
477 char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab
478 #endif
479 } Mouse;
480 struct {
481 int pointCount; // Number of touch points active
482 int pointId[MAX_TOUCH_POINTS]; // Point identifiers
483 Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen
484 char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state
485 char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state
486 } Touch;
487 struct {
488 int lastButtonPressed; // Register last gamepad button pressed
489 int axisCount; // Register number of available gamepad axis
490 bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready
491 char name[MAX_GAMEPADS][64]; // Gamepad name holder
492 char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state
493 char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state
494 float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state
495 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
496 pthread_t threadId; // Gamepad reading thread id
497 int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor
498 #endif
499 } Gamepad;
500 } Input;
501 struct {
502 double current; // Current time measure
503 double previous; // Previous time measure
504 double update; // Time measure for frame update
505 double draw; // Time measure for frame draw
506 double frame; // Time measure for one frame
507 double target; // Desired time for one frame, if 0 not applied
508 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
509 unsigned long long base; // Base time measure for hi-res timer
510 #endif
511 unsigned int frameCounter; // Frame counter
512 } Time;
513 } CoreData;
514
515 //----------------------------------------------------------------------------------
516 // Global Variables Definition
517 //----------------------------------------------------------------------------------
518 RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings
519
520 static CoreData CORE = { 0 }; // Global CORE state context
521
522 #if defined(SUPPORT_SCREEN_CAPTURE)
523 static int screenshotCounter = 0; // Screenshots counter
524 #endif
525
526 #if defined(SUPPORT_GIF_RECORDING)
527 static int gifFrameCounter = 0; // GIF frames counter
528 static bool gifRecording = false; // GIF recording state
529 static MsfGifState gifState = { 0 }; // MSGIF context state
530 #endif
531
532 #if defined(SUPPORT_EVENTS_AUTOMATION)
533 #define MAX_CODE_AUTOMATION_EVENTS 16384
534
535 typedef enum AutomationEventType {
536 EVENT_NONE = 0,
537 // Input events
538 INPUT_KEY_UP, // param[0]: key
539 INPUT_KEY_DOWN, // param[0]: key
540 INPUT_KEY_PRESSED, // param[0]: key
541 INPUT_KEY_RELEASED, // param[0]: key
542 INPUT_MOUSE_BUTTON_UP, // param[0]: button
543 INPUT_MOUSE_BUTTON_DOWN, // param[0]: button
544 INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y
545 INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta
546 INPUT_GAMEPAD_CONNECT, // param[0]: gamepad
547 INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad
548 INPUT_GAMEPAD_BUTTON_UP, // param[0]: button
549 INPUT_GAMEPAD_BUTTON_DOWN, // param[0]: button
550 INPUT_GAMEPAD_AXIS_MOTION, // param[0]: axis, param[1]: delta
551 INPUT_TOUCH_UP, // param[0]: id
552 INPUT_TOUCH_DOWN, // param[0]: id
553 INPUT_TOUCH_POSITION, // param[0]: x, param[1]: y
554 INPUT_GESTURE, // param[0]: gesture
555 // Window events
556 WINDOW_CLOSE, // no params
557 WINDOW_MAXIMIZE, // no params
558 WINDOW_MINIMIZE, // no params
559 WINDOW_RESIZE, // param[0]: width, param[1]: height
560 // Custom events
561 ACTION_TAKE_SCREENSHOT,
562 ACTION_SETTARGETFPS
563 } AutomationEventType;
564
565 // Event type
566 // Used to enable events flags
567 typedef enum {
568 EVENT_INPUT_KEYBOARD = 0,
569 EVENT_INPUT_MOUSE = 1,
570 EVENT_INPUT_GAMEPAD = 2,
571 EVENT_INPUT_TOUCH = 4,
572 EVENT_INPUT_GESTURE = 8,
573 EVENT_WINDOW = 16,
574 EVENT_CUSTOM = 32
575 } EventType;
576
577 static const char *autoEventTypeName[] = {
578 "EVENT_NONE",
579 "INPUT_KEY_UP",
580 "INPUT_KEY_DOWN",
581 "INPUT_KEY_PRESSED",
582 "INPUT_KEY_RELEASED",
583 "INPUT_MOUSE_BUTTON_UP",
584 "INPUT_MOUSE_BUTTON_DOWN",
585 "INPUT_MOUSE_POSITION",
586 "INPUT_MOUSE_WHEEL_MOTION",
587 "INPUT_GAMEPAD_CONNECT",
588 "INPUT_GAMEPAD_DISCONNECT",
589 "INPUT_GAMEPAD_BUTTON_UP",
590 "INPUT_GAMEPAD_BUTTON_DOWN",
591 "INPUT_GAMEPAD_AXIS_MOTION",
592 "INPUT_TOUCH_UP",
593 "INPUT_TOUCH_DOWN",
594 "INPUT_TOUCH_POSITION",
595 "INPUT_GESTURE",
596 "WINDOW_CLOSE",
597 "WINDOW_MAXIMIZE",
598 "WINDOW_MINIMIZE",
599 "WINDOW_RESIZE",
600 "ACTION_TAKE_SCREENSHOT",
601 "ACTION_SETTARGETFPS"
602 };
603
604 // Automation Event (24 bytes)
605 typedef struct AutomationEvent {
606 unsigned int frame; // Event frame
607 unsigned int type; // Event type (AutomationEventType)
608 int params[4]; // Event parameters (if required)
609 } AutomationEvent;
610
611 static AutomationEvent *events = NULL; // Events array
612 static unsigned int eventCount = 0; // Events count
613 static bool eventsPlaying = false; // Play events
614 static bool eventsRecording = false; // Record events
615
616 //static short eventsEnabled = 0b0000001111111111; // Events enabled for checking
617 #endif
618 //-----------------------------------------------------------------------------------
619
620 //----------------------------------------------------------------------------------
621 // Other Modules Functions Declaration (required by core)
622 //----------------------------------------------------------------------------------
623 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT)
624 extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow()
625 extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory
626 #endif
627
628 //----------------------------------------------------------------------------------
629 // Module specific Functions Declaration
630 //----------------------------------------------------------------------------------
631 static void InitTimer(void); // Initialize timer (hi-resolution if available)
632 static bool InitGraphicsDevice(int width, int height); // Initialize graphics device
633 static void SetupFramebuffer(int width, int height); // Setup main framebuffer
634 static void SetupViewport(int width, int height); // Set viewport for a provided width and height
635
636 static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path
637 static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path
638
639 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
640 static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error
641 // Window callbacks events
642 static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized
643 #if !defined(PLATFORM_WEB)
644 static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized
645 #endif
646 static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored
647 static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus
648 static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window
649 // Input callbacks events
650 static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed
651 static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value)
652 static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed
653 static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move
654 static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel
655 static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area
656 #endif
657
658 #if defined(PLATFORM_ANDROID)
659 static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands
660 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs
661 #endif
662
663 #if defined(PLATFORM_WEB)
664 static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData);
665 static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData);
666 static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData);
667
668 static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
669 static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
670 static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData);
671 #endif
672
673 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
674 static void InitKeyboard(void); // Initialize raw keyboard system
675 static void RestoreKeyboard(void); // Restore keyboard system
676 #if defined(SUPPORT_SSH_KEYBOARD_RPI)
677 static void ProcessKeyboard(void); // Process keyboard events
678 #endif
679
680 static void InitEvdevInput(void); // Initialize evdev inputs
681 static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate
682 static void PollKeyboardEvents(void); // Process evdev keyboard events.
683 static void *EventThread(void *arg); // Input device events reading thread
684
685 static void InitGamepad(void); // Initialize raw gamepad input
686 static void *GamepadThread(void *arg); // Mouse reading thread
687
688 #if defined(PLATFORM_DRM)
689 static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list
690 static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list
691 static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list
692 #endif
693
694 #endif // PLATFORM_RPI || PLATFORM_DRM
695
696 #if defined(SUPPORT_EVENTS_AUTOMATION)
697 static void LoadAutomationEvents(const char *fileName); // Load automation events from file
698 static void ExportAutomationEvents(const char *fileName); // Export recorded automation events into a file
699 static void RecordAutomationEvent(unsigned int frame); // Record frame events (to internal events array)
700 static void PlayAutomationEvent(unsigned int frame); // Play frame events (from internal events array)
701 #endif
702
703 #if defined(_WIN32)
704 // NOTE: We declare Sleep() function symbol to avoid including windows.h (kernel32.lib linkage required)
705 void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime()
706 #endif
707
708 #if !defined(SUPPORT_MODULE_RTEXT)
709 const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed'
710 #endif // !SUPPORT_MODULE_RTEXT
711
712 //----------------------------------------------------------------------------------
713 // Module Functions Definition - Window and OpenGL Context Functions
714 //----------------------------------------------------------------------------------
715 #if defined(PLATFORM_ANDROID)
716 // To allow easier porting to android, we allow the user to define a
717 // main function which we call from android_main, defined by ourselves
718 extern int main(int argc, char *argv[]);
719
720 void android_main(struct android_app *app)
721 {
722 char arg0[] = "raylib"; // NOTE: argv[] are mutable
723 CORE.Android.app = app;
724
725 // NOTE: Return from main is ignored
726 (void)main(1, (char *[]) { arg0, NULL });
727
728 // Request to end the native activity
729 ANativeActivity_finish(app->activity);
730
731 // Android ALooper_pollAll() variables
732 int pollResult = 0;
733 int pollEvents = 0;
734
735 // Waiting for application events before complete finishing
736 while (!app->destroyRequested)
737 {
738 while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&CORE.Android.source)) >= 0)
739 {
740 if (CORE.Android.source != NULL) CORE.Android.source->process(app, CORE.Android.source);
741 }
742 }
743 }
744
745 // NOTE: Add this to header (if apps really need it)
746 struct android_app *GetAndroidApp(void)
747 {
748 return CORE.Android.app;
749 }
750 #endif
751
752 // Initialize window and OpenGL context
753 // NOTE: data parameter could be used to pass any kind of required data to the initialization
754 void InitWindow(int width, int height, const char *title)
755 {
756 TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION);
757
758 TRACELOG(LOG_INFO, "Supported raylib modules:");
759 TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)");
760 TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)");
761 #if defined(SUPPORT_MODULE_RSHAPES)
762 TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)");
763 #else
764 TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)");
765 #endif
766 #if defined(SUPPORT_MODULE_RTEXTURES)
767 TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)");
768 #else
769 TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)");
770 #endif
771 #if defined(SUPPORT_MODULE_RTEXT)
772 TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)");
773 #else
774 TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)");
775 #endif
776 #if defined(SUPPORT_MODULE_RMODELS)
777 TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)");
778 #else
779 TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)");
780 #endif
781 #if defined(SUPPORT_MODULE_RAUDIO)
782 TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)");
783 #else
784 TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)");
785 #endif
786
787 if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title;
788
789 // Initialize global input state
790 memset(&CORE.Input, 0, sizeof(CORE.Input));
791 CORE.Input.Keyboard.exitKey = KEY_ESCAPE;
792 CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f };
793 CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW;
794 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
795 #if defined(SUPPORT_EVENTS_WAITING)
796 CORE.Window.eventWaiting = true;
797 #endif
798
799 #if defined(PLATFORM_ANDROID)
800 CORE.Window.screen.width = width;
801 CORE.Window.screen.height = height;
802 CORE.Window.currentFbo.width = width;
803 CORE.Window.currentFbo.height = height;
804
805 // Set desired windows flags before initializing anything
806 ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER
807
808 int orientation = AConfiguration_getOrientation(CORE.Android.app->config);
809
810 if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait");
811 else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape");
812
813 // TODO: Automatic orientation doesn't seem to work
814 if (width <= height)
815 {
816 AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT);
817 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait");
818 }
819 else
820 {
821 AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND);
822 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape");
823 }
824
825 //AConfiguration_getDensity(CORE.Android.app->config);
826 //AConfiguration_getKeyboard(CORE.Android.app->config);
827 //AConfiguration_getScreenSize(CORE.Android.app->config);
828 //AConfiguration_getScreenLong(CORE.Android.app->config);
829
830 // Initialize App command system
831 // NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()...
832 CORE.Android.app->onAppCmd = AndroidCommandCallback;
833
834 // Initialize input events system
835 CORE.Android.app->onInputEvent = AndroidInputCallback;
836
837 // Initialize assets manager
838 InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath);
839
840 // Initialize base path for storage
841 CORE.Storage.basePath = CORE.Android.app->activity->internalDataPath;
842
843 TRACELOG(LOG_INFO, "ANDROID: App initialized successfully");
844
845 // Android ALooper_pollAll() variables
846 int pollResult = 0;
847 int pollEvents = 0;
848
849 // Wait for window to be initialized (display and context)
850 while (!CORE.Window.ready)
851 {
852 // Process events loop
853 while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0)
854 {
855 // Process this event
856 if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source);
857
858 // NOTE: Never close window, native activity is controlled by the system!
859 //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true;
860 }
861 }
862 #endif
863 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
864 // Initialize graphics device (display device and OpenGL context)
865 // NOTE: returns true if window and graphic device has been initialized successfully
866 CORE.Window.ready = InitGraphicsDevice(width, height);
867
868 // If graphic device is no properly initialized, we end program
869 if (!CORE.Window.ready)
870 {
871 TRACELOG(LOG_FATAL, "Failed to initialize Graphic Device");
872 return;
873 }
874 else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2);
875
876 // Initialize hi-res timer
877 InitTimer();
878
879 // Initialize random seed
880 srand((unsigned int)time(NULL));
881
882 // Initialize base path for storage
883 CORE.Storage.basePath = GetWorkingDirectory();
884
885 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT)
886 // Load default font
887 // WARNING: External function: Module required: rtext
888 LoadFontDefault();
889 #if defined(SUPPORT_MODULE_RSHAPES)
890 Rectangle rec = GetFontDefault().recs[95];
891 // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering
892 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes
893 #endif
894 #else
895 #if defined(SUPPORT_MODULE_RSHAPES)
896 // Set default texture and rectangle to be used for shapes drawing
897 // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8
898 Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
899 SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes
900 #endif
901 #endif
902 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT)
903 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
904 {
905 // Set default font texture filter for HighDPI (blurry)
906 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
907 rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR);
908 rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR);
909 }
910 #endif
911
912 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
913 // Initialize raw input system
914 InitEvdevInput(); // Evdev inputs initialization
915 InitGamepad(); // Gamepad init
916 InitKeyboard(); // Keyboard init (stdin)
917 #endif
918
919 #if defined(PLATFORM_WEB)
920 // Setup callback functions for the DOM events
921 emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback);
922
923 // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review
924 // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas)
925 //emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback);
926 // Check Resize event (note this is done on the window since most browsers don't support this on #canvas)
927 //emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback);
928 // Trigger this once to get initial window sizing
929 //EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL);
930
931 // Support keyboard events -> Not used, GLFW.JS takes care of that
932 //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback);
933 //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback);
934
935 // Support mouse events
936 emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback);
937
938 // Support touch events
939 emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
940 emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
941 emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
942 emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
943
944 // Support gamepad events (not provided by GLFW3 on emscripten)
945 emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback);
946 emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback);
947 #endif
948
949 #if defined(SUPPORT_EVENTS_AUTOMATION)
950 events = (AutomationEvent *)RL_CALLOC(MAX_CODE_AUTOMATION_EVENTS, sizeof(AutomationEvent));
951 CORE.Time.frameCounter = 0;
952 #endif
953
954 #endif // PLATFORM_DESKTOP || PLATFORM_WEB || PLATFORM_RPI || PLATFORM_DRM
955 }
956
957 // Close window and unload OpenGL context
958 void CloseWindow(void)
959 {
960 #if defined(SUPPORT_GIF_RECORDING)
961 if (gifRecording)
962 {
963 MsfGifResult result = msf_gif_end(&gifState);
964 msf_gif_free(result);
965 gifRecording = false;
966 }
967 #endif
968
969 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT)
970 UnloadFontDefault(); // WARNING: Module required: rtext
971 #endif
972
973 rlglClose(); // De-init rlgl
974
975 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
976 glfwDestroyWindow(CORE.Window.handle);
977 glfwTerminate();
978 #endif
979
980 #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP)
981 timeEndPeriod(1); // Restore time period
982 #endif
983
984 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
985 // Close surface, context and display
986 if (CORE.Window.device != EGL_NO_DISPLAY)
987 {
988 eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
989
990 if (CORE.Window.surface != EGL_NO_SURFACE)
991 {
992 eglDestroySurface(CORE.Window.device, CORE.Window.surface);
993 CORE.Window.surface = EGL_NO_SURFACE;
994 }
995
996 if (CORE.Window.context != EGL_NO_CONTEXT)
997 {
998 eglDestroyContext(CORE.Window.device, CORE.Window.context);
999 CORE.Window.context = EGL_NO_CONTEXT;
1000 }
1001
1002 eglTerminate(CORE.Window.device);
1003 CORE.Window.device = EGL_NO_DISPLAY;
1004 }
1005 #endif
1006
1007 #if defined(PLATFORM_DRM)
1008 if (CORE.Window.prevFB)
1009 {
1010 drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB);
1011 CORE.Window.prevFB = 0;
1012 }
1013
1014 if (CORE.Window.prevBO)
1015 {
1016 gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO);
1017 CORE.Window.prevBO = NULL;
1018 }
1019
1020 if (CORE.Window.gbmSurface)
1021 {
1022 gbm_surface_destroy(CORE.Window.gbmSurface);
1023 CORE.Window.gbmSurface = NULL;
1024 }
1025
1026 if (CORE.Window.gbmDevice)
1027 {
1028 gbm_device_destroy(CORE.Window.gbmDevice);
1029 CORE.Window.gbmDevice = NULL;
1030 }
1031
1032 if (CORE.Window.crtc)
1033 {
1034 if (CORE.Window.connector)
1035 {
1036 drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id,
1037 CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode);
1038 drmModeFreeConnector(CORE.Window.connector);
1039 CORE.Window.connector = NULL;
1040 }
1041
1042 drmModeFreeCrtc(CORE.Window.crtc);
1043 CORE.Window.crtc = NULL;
1044 }
1045
1046 if (CORE.Window.fd != -1)
1047 {
1048 close(CORE.Window.fd);
1049 CORE.Window.fd = -1;
1050 }
1051
1052 // Close surface, context and display
1053 if (CORE.Window.device != EGL_NO_DISPLAY)
1054 {
1055 if (CORE.Window.surface != EGL_NO_SURFACE)
1056 {
1057 eglDestroySurface(CORE.Window.device, CORE.Window.surface);
1058 CORE.Window.surface = EGL_NO_SURFACE;
1059 }
1060
1061 if (CORE.Window.context != EGL_NO_CONTEXT)
1062 {
1063 eglDestroyContext(CORE.Window.device, CORE.Window.context);
1064 CORE.Window.context = EGL_NO_CONTEXT;
1065 }
1066
1067 eglTerminate(CORE.Window.device);
1068 CORE.Window.device = EGL_NO_DISPLAY;
1069 }
1070 #endif
1071
1072 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
1073 // Wait for mouse and gamepad threads to finish before closing
1074 // NOTE: Those threads should already have finished at this point
1075 // because they are controlled by CORE.Window.shouldClose variable
1076
1077 CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called
1078
1079 // Close the evdev keyboard
1080 if (CORE.Input.Keyboard.fd != -1)
1081 {
1082 close(CORE.Input.Keyboard.fd);
1083 CORE.Input.Keyboard.fd = -1;
1084 }
1085
1086 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
1087 {
1088 if (CORE.Input.eventWorker[i].threadId)
1089 {
1090 pthread_join(CORE.Input.eventWorker[i].threadId, NULL);
1091 }
1092 }
1093
1094 if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL);
1095 #endif
1096
1097 #if defined(SUPPORT_EVENTS_AUTOMATION)
1098 RL_FREE(events);
1099 #endif
1100
1101 CORE.Window.ready = false;
1102 TRACELOG(LOG_INFO, "Window closed successfully");
1103 }
1104
1105 // Check if KEY_ESCAPE pressed or Close icon pressed
1106 bool WindowShouldClose(void)
1107 {
1108 #if defined(PLATFORM_WEB)
1109 // Emterpreter-Async required to run sync code
1110 // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code
1111 // By default, this function is never called on a web-ready raylib example because we encapsulate
1112 // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously
1113 // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter!
1114 emscripten_sleep(16);
1115 return false;
1116 #endif
1117
1118 #if defined(PLATFORM_DESKTOP)
1119 if (CORE.Window.ready)
1120 {
1121 // While window minimized, stop loop execution
1122 while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents();
1123
1124 CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle);
1125
1126 // Reset close status for next frame
1127 glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE);
1128
1129 return CORE.Window.shouldClose;
1130 }
1131 else return true;
1132 #endif
1133
1134 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
1135 if (CORE.Window.ready) return CORE.Window.shouldClose;
1136 else return true;
1137 #endif
1138 }
1139
1140 // Check if window has been initialized successfully
1141 bool IsWindowReady(void)
1142 {
1143 return CORE.Window.ready;
1144 }
1145
1146 // Check if window is currently fullscreen
1147 bool IsWindowFullscreen(void)
1148 {
1149 return CORE.Window.fullscreen;
1150 }
1151
1152 // Check if window is currently hidden
1153 bool IsWindowHidden(void)
1154 {
1155 #if defined(PLATFORM_DESKTOP)
1156 return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0);
1157 #endif
1158 return false;
1159 }
1160
1161 // Check if window has been minimized
1162 bool IsWindowMinimized(void)
1163 {
1164 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
1165 return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0);
1166 #endif
1167 return false;
1168 }
1169
1170 // Check if window has been maximized (only PLATFORM_DESKTOP)
1171 bool IsWindowMaximized(void)
1172 {
1173 #if defined(PLATFORM_DESKTOP)
1174 return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0);
1175 #endif
1176 return false;
1177 }
1178
1179 // Check if window has the focus
1180 bool IsWindowFocused(void)
1181 {
1182 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
1183 return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0);
1184 #endif
1185 #if defined(PLATFORM_ANDROID)
1186 return CORE.Android.appEnabled;
1187 #endif
1188 return true;
1189 }
1190
1191 // Check if window has been resizedLastFrame
1192 bool IsWindowResized(void)
1193 {
1194 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
1195 return CORE.Window.resizedLastFrame;
1196 #else
1197 return false;
1198 #endif
1199 }
1200
1201 // Check if one specific window flag is enabled
1202 bool IsWindowState(unsigned int flag)
1203 {
1204 return ((CORE.Window.flags & flag) > 0);
1205 }
1206
1207 // Toggle fullscreen mode (only PLATFORM_DESKTOP)
1208 void ToggleFullscreen(void)
1209 {
1210 #if defined(PLATFORM_DESKTOP)
1211 if (!CORE.Window.fullscreen)
1212 {
1213 // Store previous window position (in case we exit fullscreen)
1214 glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y);
1215
1216 int monitorCount = 0;
1217 int monitorIndex = GetCurrentMonitor();
1218 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1219
1220 // Use current monitor, so we correctly get the display the window is on
1221 GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL;
1222
1223 if (monitor == NULL)
1224 {
1225 TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor");
1226
1227 CORE.Window.fullscreen = false;
1228 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE;
1229
1230 glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
1231 }
1232 else
1233 {
1234 CORE.Window.fullscreen = true;
1235 CORE.Window.flags |= FLAG_FULLSCREEN_MODE;
1236
1237 glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
1238 }
1239 }
1240 else
1241 {
1242 CORE.Window.fullscreen = false;
1243 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE;
1244
1245 glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
1246 }
1247
1248 // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS)
1249 // NOTE: V-Sync can be enabled by graphic driver configuration
1250 if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1);
1251 #endif
1252 #if defined(PLATFORM_WEB)
1253 /*
1254 EM_ASM
1255 (
1256 // This strategy works well while using raylib minimal web shell for emscripten,
1257 // it re-scales the canvas to fullscreen using monitor resolution, for tools this
1258 // is a good strategy but maybe games prefer to keep current canvas resolution and
1259 // display it in fullscreen, adjusting monitor resolution if possible
1260 if (document.fullscreenElement) document.exitFullscreen();
1261 else Module.requestFullscreen(true, true); //false, true);
1262 );
1263 */
1264 //EM_ASM(Module.requestFullscreen(false, false););
1265 /*
1266 if (!CORE.Window.fullscreen)
1267 {
1268 // Option 1: Request fullscreen for the canvas element
1269 // This option does not seem to work at all:
1270 // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security,
1271 // the user must click once on the canvas to hide the pointer or transition to full screen
1272 //emscripten_request_fullscreen("#canvas", false);
1273
1274 // Option 2: Request fullscreen for the canvas element with strategy
1275 // This option does not seem to work at all
1276 // Ref: https://github.com/emscripten-core/emscripten/issues/5124
1277 // EmscriptenFullscreenStrategy strategy = {
1278 // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT,
1279 // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF,
1280 // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT,
1281 // .canvasResizedCallback = EmscriptenWindowResizedCallback,
1282 // .canvasResizedCallbackUserData = NULL
1283 // };
1284 //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy);
1285
1286 // Option 3: Request fullscreen for the canvas element with strategy
1287 // It works as expected but only inside the browser (client area)
1288 EmscriptenFullscreenStrategy strategy = {
1289 .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT,
1290 .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF,
1291 .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT,
1292 .canvasResizedCallback = EmscriptenWindowResizedCallback,
1293 .canvasResizedCallbackUserData = NULL
1294 };
1295 emscripten_enter_soft_fullscreen("#canvas", &strategy);
1296
1297 int width, height;
1298 emscripten_get_canvas_element_size("#canvas", &width, &height);
1299 TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height);
1300
1301 CORE.Window.fullscreen = true; // Toggle fullscreen flag
1302 CORE.Window.flags |= FLAG_FULLSCREEN_MODE;
1303 }
1304 else
1305 {
1306 //emscripten_exit_fullscreen();
1307 //emscripten_exit_soft_fullscreen();
1308
1309 int width, height;
1310 emscripten_get_canvas_element_size("#canvas", &width, &height);
1311 TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height);
1312
1313 CORE.Window.fullscreen = false; // Toggle fullscreen flag
1314 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE;
1315 }
1316 */
1317
1318 CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag
1319 #endif
1320 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
1321 TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode");
1322 #endif
1323 }
1324
1325 // Toggle borderless windowed mode (only PLATFORM_DESKTOP)
1326 void ToggleBorderlessWindowed(void)
1327 {
1328 #if defined(PLATFORM_DESKTOP)
1329 // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it
1330 bool wasOnFullscreen = false;
1331 if (CORE.Window.fullscreen)
1332 {
1333 CORE.Window.previousPosition = CORE.Window.position;
1334 ToggleFullscreen();
1335 wasOnFullscreen = true;
1336 }
1337
1338 const int monitor = GetCurrentMonitor();
1339 int monitorCount;
1340 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1341 if ((monitor >= 0) && (monitor < monitorCount))
1342 {
1343 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
1344 if (mode)
1345 {
1346 if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE))
1347 {
1348 // Store screen position and size
1349 // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
1350 if (!wasOnFullscreen) glfwGetWindowPos(CORE.Window.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
1351 CORE.Window.previousScreen = CORE.Window.screen;
1352
1353 // Set undecorated and topmost modes and flags
1354 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE);
1355 CORE.Window.flags |= FLAG_WINDOW_UNDECORATED;
1356 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE);
1357 CORE.Window.flags |= FLAG_WINDOW_TOPMOST;
1358
1359 // Get monitor position and size
1360 int monitorPosX = 0;
1361 int monitorPosY = 0;
1362 glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY);
1363 const int monitorWidth = mode->width;
1364 const int monitorHeight = mode->height;
1365 glfwSetWindowSize(CORE.Window.handle, monitorWidth, monitorHeight);
1366
1367 // Set screen position and size
1368 glfwSetWindowPos(CORE.Window.handle, monitorPosX, monitorPosY);
1369 glfwSetWindowSize(CORE.Window.handle, monitorWidth, monitorHeight);
1370
1371 // Refocus window
1372 glfwFocusWindow(CORE.Window.handle);
1373
1374 CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE;
1375 }
1376 else
1377 {
1378 // Remove topmost and undecorated modes and flags
1379 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE);
1380 CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST;
1381 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE);
1382 CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED;
1383
1384 // Return previous screen size and position
1385 // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly
1386 glfwSetWindowSize(CORE.Window.handle, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height);
1387 glfwSetWindowPos(CORE.Window.handle, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y);
1388
1389 // Refocus window
1390 glfwFocusWindow(CORE.Window.handle);
1391
1392 CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE;
1393 }
1394 }
1395 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
1396 }
1397 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
1398 #endif
1399 }
1400
1401 // Set window state: maximized, if resizable (only PLATFORM_DESKTOP)
1402 void MaximizeWindow(void)
1403 {
1404 #if defined(PLATFORM_DESKTOP)
1405 if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE)
1406 {
1407 glfwMaximizeWindow(CORE.Window.handle);
1408 CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED;
1409 }
1410 #endif
1411 }
1412
1413 // Set window state: minimized (only PLATFORM_DESKTOP)
1414 void MinimizeWindow(void)
1415 {
1416 #if defined(PLATFORM_DESKTOP)
1417 // NOTE: Following function launches callback that sets appropriate flag!
1418 glfwIconifyWindow(CORE.Window.handle);
1419 #endif
1420 }
1421
1422 // Set window state: not minimized/maximized (only PLATFORM_DESKTOP)
1423 void RestoreWindow(void)
1424 {
1425 #if defined(PLATFORM_DESKTOP)
1426 if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE)
1427 {
1428 // Restores the specified window if it was previously iconified (minimized) or maximized
1429 glfwRestoreWindow(CORE.Window.handle);
1430 CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED;
1431 CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED;
1432 }
1433 #endif
1434 }
1435
1436 // Set window configuration state using flags
1437 void SetWindowState(unsigned int flags)
1438 {
1439 #if defined(PLATFORM_DESKTOP)
1440 // Check previous state and requested state to apply required changes
1441 // NOTE: In most cases the functions already change the flags internally
1442
1443 // State change: FLAG_VSYNC_HINT
1444 if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0))
1445 {
1446 glfwSwapInterval(1);
1447 CORE.Window.flags |= FLAG_VSYNC_HINT;
1448 }
1449
1450 // State change: FLAG_BORDERLESS_WINDOWED_MODE
1451 // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running
1452 if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) != (flags & FLAG_BORDERLESS_WINDOWED_MODE)) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0))
1453 {
1454 ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function
1455 }
1456
1457 // State change: FLAG_FULLSCREEN_MODE
1458 if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE))
1459 {
1460 ToggleFullscreen(); // NOTE: Window state flag updated inside function
1461 }
1462
1463 // State change: FLAG_WINDOW_RESIZABLE
1464 if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0))
1465 {
1466 glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE);
1467 CORE.Window.flags |= FLAG_WINDOW_RESIZABLE;
1468 }
1469
1470 // State change: FLAG_WINDOW_UNDECORATED
1471 if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED))
1472 {
1473 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE);
1474 CORE.Window.flags |= FLAG_WINDOW_UNDECORATED;
1475 }
1476
1477 // State change: FLAG_WINDOW_HIDDEN
1478 if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0))
1479 {
1480 glfwHideWindow(CORE.Window.handle);
1481 CORE.Window.flags |= FLAG_WINDOW_HIDDEN;
1482 }
1483
1484 // State change: FLAG_WINDOW_MINIMIZED
1485 if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0))
1486 {
1487 //GLFW_ICONIFIED
1488 MinimizeWindow(); // NOTE: Window state flag updated inside function
1489 }
1490
1491 // State change: FLAG_WINDOW_MAXIMIZED
1492 if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0))
1493 {
1494 //GLFW_MAXIMIZED
1495 MaximizeWindow(); // NOTE: Window state flag updated inside function
1496 }
1497
1498 // State change: FLAG_WINDOW_UNFOCUSED
1499 if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0))
1500 {
1501 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE);
1502 CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED;
1503 }
1504
1505 // State change: FLAG_WINDOW_TOPMOST
1506 if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0))
1507 {
1508 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE);
1509 CORE.Window.flags |= FLAG_WINDOW_TOPMOST;
1510 }
1511
1512 // State change: FLAG_WINDOW_ALWAYS_RUN
1513 if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0))
1514 {
1515 CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN;
1516 }
1517
1518 // The following states can not be changed after window creation
1519
1520 // State change: FLAG_WINDOW_TRANSPARENT
1521 if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0))
1522 {
1523 TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization");
1524 }
1525
1526 // State change: FLAG_WINDOW_HIGHDPI
1527 if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0))
1528 {
1529 TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization");
1530 }
1531
1532 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH
1533 if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) != (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH)) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0))
1534 {
1535 glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE);
1536 CORE.Window.flags |= FLAG_WINDOW_MOUSE_PASSTHROUGH;
1537 }
1538
1539 // State change: FLAG_MSAA_4X_HINT
1540 if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0))
1541 {
1542 TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization");
1543 }
1544
1545 // State change: FLAG_INTERLACED_HINT
1546 if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0))
1547 {
1548 TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization");
1549 }
1550 #endif
1551 }
1552
1553 // Clear window configuration state flags
1554 void ClearWindowState(unsigned int flags)
1555 {
1556 #if defined(PLATFORM_DESKTOP)
1557 // Check previous state and requested state to apply required changes
1558 // NOTE: In most cases the functions already change the flags internally
1559
1560 // State change: FLAG_VSYNC_HINT
1561 if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0))
1562 {
1563 glfwSwapInterval(0);
1564 CORE.Window.flags &= ~FLAG_VSYNC_HINT;
1565 }
1566
1567 // State change: FLAG_BORDERLESS_WINDOWED_MODE
1568 // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running
1569 if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0))
1570 {
1571 ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function
1572 }
1573
1574 // State change: FLAG_FULLSCREEN_MODE
1575 if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0))
1576 {
1577 ToggleFullscreen(); // NOTE: Window state flag updated inside function
1578 }
1579
1580 // State change: FLAG_WINDOW_RESIZABLE
1581 if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0))
1582 {
1583 glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE);
1584 CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE;
1585 }
1586
1587 // State change: FLAG_WINDOW_HIDDEN
1588 if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0))
1589 {
1590 glfwShowWindow(CORE.Window.handle);
1591 CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN;
1592 }
1593
1594 // State change: FLAG_WINDOW_MINIMIZED
1595 if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0))
1596 {
1597 RestoreWindow(); // NOTE: Window state flag updated inside function
1598 }
1599
1600 // State change: FLAG_WINDOW_MAXIMIZED
1601 if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0))
1602 {
1603 RestoreWindow(); // NOTE: Window state flag updated inside function
1604 }
1605
1606 // State change: FLAG_WINDOW_UNDECORATED
1607 if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0))
1608 {
1609 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE);
1610 CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED;
1611 }
1612
1613 // State change: FLAG_WINDOW_UNFOCUSED
1614 if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0))
1615 {
1616 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE);
1617 CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED;
1618 }
1619
1620 // State change: FLAG_WINDOW_TOPMOST
1621 if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0))
1622 {
1623 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE);
1624 CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST;
1625 }
1626
1627 // State change: FLAG_WINDOW_ALWAYS_RUN
1628 if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0))
1629 {
1630 CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN;
1631 }
1632
1633 // The following states can not be changed after window creation
1634
1635 // State change: FLAG_WINDOW_TRANSPARENT
1636 if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0))
1637 {
1638 TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization");
1639 }
1640
1641 // State change: FLAG_WINDOW_HIGHDPI
1642 if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0))
1643 {
1644 TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization");
1645 }
1646
1647 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH
1648 if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0))
1649 {
1650 glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE);
1651 CORE.Window.flags &= ~FLAG_WINDOW_MOUSE_PASSTHROUGH;
1652 }
1653
1654 // State change: FLAG_MSAA_4X_HINT
1655 if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0))
1656 {
1657 TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization");
1658 }
1659
1660 // State change: FLAG_INTERLACED_HINT
1661 if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0))
1662 {
1663 TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization");
1664 }
1665 #endif
1666 }
1667
1668 // Set icon for window (only PLATFORM_DESKTOP)
1669 // NOTE 1: Image must be in RGBA format, 8bit per channel
1670 // NOTE 2: Image is scaled by the OS for all required sizes
1671 void SetWindowIcon(Image image)
1672 {
1673 #if defined(PLATFORM_DESKTOP)
1674 if (image.data == NULL)
1675 {
1676 // Revert to the default window icon, pass in an empty image array
1677 glfwSetWindowIcon(CORE.Window.handle, 0, NULL);
1678 }
1679 else
1680 {
1681 if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)
1682 {
1683 GLFWimage icon[1] = { 0 };
1684
1685 icon[0].width = image.width;
1686 icon[0].height = image.height;
1687 icon[0].pixels = (unsigned char *)image.data;
1688
1689 // NOTE 1: We only support one image icon
1690 // NOTE 2: The specified image data is copied before this function returns
1691 glfwSetWindowIcon(CORE.Window.handle, 1, icon);
1692 }
1693 else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format");
1694 }
1695 #endif
1696 }
1697
1698 // Set icon for window (multiple images, only PLATFORM_DESKTOP)
1699 // NOTE 1: Images must be in RGBA format, 8bit per channel
1700 // NOTE 2: The multiple images are used depending on provided sizes
1701 // Standard Windows icon sizes: 256, 128, 96, 64, 48, 32, 24, 16
1702 void SetWindowIcons(Image *images, int count)
1703 {
1704 #if defined(PLATFORM_DESKTOP)
1705 if ((images == NULL) || (count <= 0))
1706 {
1707 // Revert to the default window icon, pass in an empty image array
1708 glfwSetWindowIcon(CORE.Window.handle, 0, NULL);
1709 }
1710 else
1711 {
1712 int valid = 0;
1713 GLFWimage *icons = RL_CALLOC(count, sizeof(GLFWimage));
1714
1715 for (int i = 0; i < count; i++)
1716 {
1717 if (images[i].format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)
1718 {
1719 icons[valid].width = images[i].width;
1720 icons[valid].height = images[i].height;
1721 icons[valid].pixels = (unsigned char *)images[i].data;
1722
1723 valid++;
1724 }
1725 else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format");
1726 }
1727 // NOTE: Images data is copied internally before this function returns
1728 glfwSetWindowIcon(CORE.Window.handle, valid, icons);
1729
1730 RL_FREE(icons);
1731 }
1732 #endif
1733 }
1734
1735 // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB)
1736 void SetWindowTitle(const char *title)
1737 {
1738 CORE.Window.title = title;
1739 #if defined(PLATFORM_DESKTOP)
1740 glfwSetWindowTitle(CORE.Window.handle, title);
1741 #endif
1742 #if defined(PLATFORM_WEB)
1743 emscripten_set_window_title(title);
1744 #endif
1745 }
1746
1747 // Set window position on screen (windowed mode)
1748 void SetWindowPosition(int x, int y)
1749 {
1750 #if defined(PLATFORM_DESKTOP)
1751 glfwSetWindowPos(CORE.Window.handle, x, y);
1752 #endif
1753 }
1754
1755 // Set monitor for the current window
1756 void SetWindowMonitor(int monitor)
1757 {
1758 #if defined(PLATFORM_DESKTOP)
1759 int monitorCount = 0;
1760 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1761
1762 if ((monitor >= 0) && (monitor < monitorCount))
1763 {
1764 if (CORE.Window.fullscreen)
1765 {
1766 TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor]));
1767
1768 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
1769 glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate);
1770 }
1771 else
1772 {
1773 TRACELOG(LOG_INFO, "GLFW: Selected monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor]));
1774
1775 const int screenWidth = CORE.Window.screen.width;
1776 const int screenHeight = CORE.Window.screen.height;
1777 int monitorWorkareaX = 0;
1778 int monitorWorkareaY = 0;
1779 int monitorWorkareaWidth = 0;
1780 int monitorWorkareaHeight = 0;
1781 glfwGetMonitorWorkarea(monitors[monitor], &monitorWorkareaX, &monitorWorkareaY, &monitorWorkareaWidth, &monitorWorkareaHeight);
1782
1783 // If the screen size is larger than the monitor workarea, anchor it on the top left corner, otherwise, center it
1784 if ((screenWidth >= monitorWorkareaWidth) || (screenHeight >= monitorWorkareaHeight)) glfwSetWindowPos(CORE.Window.handle, monitorWorkareaX, monitorWorkareaY);
1785 else
1786 {
1787 const int x = monitorWorkareaX + (monitorWorkareaWidth/2) - (screenWidth/2);
1788 const int y = monitorWorkareaY + (monitorWorkareaHeight/2) - (screenHeight/2);
1789 glfwSetWindowPos(CORE.Window.handle, x, y);
1790 }
1791 }
1792 }
1793 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
1794 #endif
1795 }
1796
1797 // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
1798 void SetWindowMinSize(int width, int height)
1799 {
1800 #if defined(PLATFORM_DESKTOP)
1801 const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
1802 glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height);
1803 #endif
1804 }
1805
1806 // Set window dimensions
1807 void SetWindowSize(int width, int height)
1808 {
1809 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
1810 glfwSetWindowSize(CORE.Window.handle, width, height);
1811 #endif
1812 }
1813
1814 // Set window opacity, value opacity is between 0.0 and 1.0
1815 void SetWindowOpacity(float opacity)
1816 {
1817 #if defined(PLATFORM_DESKTOP)
1818 if (opacity >= 1.0f) opacity = 1.0f;
1819 else if (opacity <= 0.0f) opacity = 0.0f;
1820 glfwSetWindowOpacity(CORE.Window.handle, opacity);
1821 #endif
1822 }
1823
1824 // Set window focused
1825 void SetWindowFocused(void)
1826 {
1827 #if defined(PLATFORM_DESKTOP)
1828 glfwFocusWindow(CORE.Window.handle);
1829 #endif
1830 }
1831
1832 // Get current screen width
1833 int GetScreenWidth(void)
1834 {
1835 return CORE.Window.screen.width;
1836 }
1837
1838 // Get current screen height
1839 int GetScreenHeight(void)
1840 {
1841 return CORE.Window.screen.height;
1842 }
1843
1844 // Get current render width which is equal to screen width * dpi scale
1845 int GetRenderWidth(void)
1846 {
1847 return CORE.Window.render.width;
1848 }
1849
1850 // Get current screen height which is equal to screen height * dpi scale
1851 int GetRenderHeight(void)
1852 {
1853 return CORE.Window.render.height;
1854 }
1855
1856 // Get native window handle
1857 void *GetWindowHandle(void)
1858 {
1859 #if defined(PLATFORM_DESKTOP) && defined(_WIN32)
1860 // NOTE: Returned handle is: void *HWND (windows.h)
1861 return glfwGetWin32Window(CORE.Window.handle);
1862 #endif
1863 #if defined(PLATFORM_DESKTOP) && defined(__linux__)
1864 // NOTE: Returned handle is: unsigned long Window (X.h)
1865 // typedef unsigned long XID;
1866 // typedef XID Window;
1867 //unsigned long id = (unsigned long)glfwGetX11Window(CORE.Window.handle);
1868 //return NULL; // TODO: Find a way to return value... cast to void *?
1869 return (void *)CORE.Window.handle;
1870 #endif
1871 #if defined(__APPLE__)
1872 // NOTE: Returned handle is: (objc_object *)
1873 return (void *)glfwGetCocoaWindow(CORE.Window.handle);
1874 #endif
1875
1876 return NULL;
1877 }
1878
1879 // Get number of monitors
1880 int GetMonitorCount(void)
1881 {
1882 #if defined(PLATFORM_DESKTOP)
1883 int monitorCount;
1884 glfwGetMonitors(&monitorCount);
1885 return monitorCount;
1886 #else
1887 return 1;
1888 #endif
1889 }
1890
1891 // Get number of monitors
1892 int GetCurrentMonitor(void)
1893 {
1894 int index = 0;
1895
1896 #if defined(PLATFORM_DESKTOP)
1897 int monitorCount;
1898 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1899 GLFWmonitor *monitor = NULL;
1900
1901 if (monitorCount > 1)
1902 {
1903 if (IsWindowFullscreen())
1904 {
1905 // Get the handle of the monitor that the specified window is in full screen on
1906 monitor = glfwGetWindowMonitor(CORE.Window.handle);
1907
1908 for (int i = 0; i < monitorCount; i++)
1909 {
1910 if (monitors[i] == monitor)
1911 {
1912 index = i;
1913 break;
1914 }
1915 }
1916 }
1917 else
1918 {
1919 int x = 0;
1920 int y = 0;
1921
1922 glfwGetWindowPos(CORE.Window.handle, &x, &y);
1923
1924 for (int i = 0; i < monitorCount; i++)
1925 {
1926 int mx = 0;
1927 int my = 0;
1928
1929 monitor = monitors[i];
1930 glfwGetMonitorPos(monitor, &mx, &my);
1931 const GLFWvidmode *mode = glfwGetVideoMode(monitor);
1932 if (mode)
1933 {
1934 const int width = mode->width;
1935 const int height = mode->height;
1936
1937 if ((x >= mx) &&
1938 (x < (mx + width)) &&
1939 (y >= my) &&
1940 (y < (my + height)))
1941 {
1942 index = i;
1943 break;
1944 }
1945 }
1946 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
1947 }
1948 }
1949 }
1950 #endif
1951
1952 return index;
1953 }
1954
1955 // Get selected monitor position
1956 Vector2 GetMonitorPosition(int monitor)
1957 {
1958 #if defined(PLATFORM_DESKTOP)
1959 int monitorCount;
1960 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1961
1962 if ((monitor >= 0) && (monitor < monitorCount))
1963 {
1964 int x, y;
1965 glfwGetMonitorPos(monitors[monitor], &x, &y);
1966
1967 return (Vector2){ (float)x, (float)y };
1968 }
1969 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
1970 #endif
1971 return (Vector2){ 0, 0 };
1972 }
1973
1974 // Get selected monitor width (currently used by monitor)
1975 int GetMonitorWidth(int monitor)
1976 {
1977 #if defined(PLATFORM_DESKTOP)
1978 int monitorCount;
1979 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
1980
1981 if ((monitor >= 0) && (monitor < monitorCount))
1982 {
1983 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
1984
1985 if (mode) return mode->width;
1986 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
1987 }
1988 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
1989 #endif
1990 #if defined(PLATFORM_ANDROID)
1991 if (CORE.Android.app->window != NULL)
1992 {
1993 return ANativeWindow_getWidth(CORE.Android.app->window);
1994 }
1995 #endif
1996 return 0;
1997 }
1998
1999 // Get selected monitor height (currently used by monitor)
2000 int GetMonitorHeight(int monitor)
2001 {
2002 #if defined(PLATFORM_DESKTOP)
2003 int monitorCount;
2004 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2005
2006 if ((monitor >= 0) && (monitor < monitorCount))
2007 {
2008 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
2009
2010 if (mode) return mode->height;
2011 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
2012 }
2013 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
2014 #endif
2015 #if defined(PLATFORM_ANDROID)
2016 if (CORE.Android.app->window != NULL)
2017 {
2018 return ANativeWindow_getHeight(CORE.Android.app->window);
2019 }
2020 #endif
2021 return 0;
2022 }
2023
2024 // Get selected monitor physical width in millimetres
2025 int GetMonitorPhysicalWidth(int monitor)
2026 {
2027 #if defined(PLATFORM_DESKTOP)
2028 int monitorCount;
2029 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2030
2031 if ((monitor >= 0) && (monitor < monitorCount))
2032 {
2033 int physicalWidth;
2034 glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL);
2035 return physicalWidth;
2036 }
2037 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
2038 #endif
2039 return 0;
2040 }
2041
2042 // Get selected monitor physical height in millimetres
2043 int GetMonitorPhysicalHeight(int monitor)
2044 {
2045 #if defined(PLATFORM_DESKTOP)
2046 int monitorCount;
2047 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2048
2049 if ((monitor >= 0) && (monitor < monitorCount))
2050 {
2051 int physicalHeight;
2052 glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight);
2053 return physicalHeight;
2054 }
2055 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
2056 #endif
2057 return 0;
2058 }
2059
2060 // Get selected monitor refresh rate
2061 int GetMonitorRefreshRate(int monitor)
2062 {
2063 #if defined(PLATFORM_DESKTOP)
2064 int monitorCount;
2065 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2066
2067 if ((monitor >= 0) && (monitor < monitorCount))
2068 {
2069 const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]);
2070 return vidmode->refreshRate;
2071 }
2072 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
2073 #endif
2074 #if defined(PLATFORM_DRM)
2075 if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0))
2076 {
2077 return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh;
2078 }
2079 #endif
2080 return 0;
2081 }
2082
2083 // Get window position XY on monitor
2084 Vector2 GetWindowPosition(void)
2085 {
2086 int x = 0;
2087 int y = 0;
2088 #if defined(PLATFORM_DESKTOP)
2089 glfwGetWindowPos(CORE.Window.handle, &x, &y);
2090 #endif
2091 return (Vector2){ (float)x, (float)y };
2092 }
2093
2094 // Get window scale DPI factor for current monitor
2095 Vector2 GetWindowScaleDPI(void)
2096 {
2097 Vector2 scale = { 1.0f, 1.0f };
2098
2099 #if defined(PLATFORM_DESKTOP)
2100 float xdpi = 1.0;
2101 float ydpi = 1.0;
2102 Vector2 windowPos = GetWindowPosition();
2103
2104 int monitorCount = 0;
2105 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2106
2107 // Check window monitor
2108 for (int i = 0; i < monitorCount; i++)
2109 {
2110 glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi);
2111
2112 int xpos, ypos, width, height;
2113 glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height);
2114
2115 if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) &&
2116 (windowPos.y >= ypos) && (windowPos.y < ypos + height))
2117 {
2118 scale.x = xdpi;
2119 scale.y = ydpi;
2120 break;
2121 }
2122 }
2123 #endif
2124
2125 return scale;
2126 }
2127
2128 // Get the human-readable, UTF-8 encoded name of the selected monitor
2129 const char *GetMonitorName(int monitor)
2130 {
2131 #if defined(PLATFORM_DESKTOP)
2132 int monitorCount;
2133 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
2134
2135 if ((monitor >= 0) && (monitor < monitorCount))
2136 {
2137 return glfwGetMonitorName(monitors[monitor]);
2138 }
2139 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
2140 #endif
2141 return "";
2142 }
2143
2144 // Set clipboard text content
2145 void SetClipboardText(const char *text)
2146 {
2147 #if defined(PLATFORM_DESKTOP)
2148 glfwSetClipboardString(CORE.Window.handle, text);
2149 #endif
2150 #if defined(PLATFORM_WEB)
2151 // Security check to (partially) avoid malicious code
2152 if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character");
2153 else EM_ASM( { navigator.clipboard.writeText(UTF8ToString($0)); }, text);
2154 #endif
2155 }
2156
2157 // Get clipboard text content
2158 // NOTE: returned string is allocated and freed by GLFW
2159 const char *GetClipboardText(void)
2160 {
2161 #if defined(PLATFORM_DESKTOP)
2162 return glfwGetClipboardString(CORE.Window.handle);
2163 #endif
2164 #if defined(PLATFORM_WEB)
2165 /*
2166 // Accessing clipboard data from browser is tricky due to security reasons
2167 // The method to use is navigator.clipboard.readText() but this is an asynchronous method
2168 // that will return at some moment after the function is called with the required data
2169 emscripten_run_script_string("navigator.clipboard.readText() \
2170 .then(text => { document.getElementById('clipboard').innerText = text; console.log('Pasted content: ', text); }) \
2171 .catch(err => { console.error('Failed to read clipboard contents: ', err); });"
2172 );
2173
2174 // The main issue is getting that data, one approach could be using ASYNCIFY and wait
2175 // for the data but it requires adding Asyncify emscripten library on compilation
2176
2177 // Another approach could be just copy the data in a HTML text field and try to retrieve it
2178 // later on if available... and clean it for future accesses
2179 */
2180 return NULL;
2181 #endif
2182 return NULL;
2183 }
2184
2185 // Enable waiting for events on EndDrawing(), no automatic event polling
2186 void EnableEventWaiting(void)
2187 {
2188 CORE.Window.eventWaiting = true;
2189 }
2190
2191 // Disable waiting for events on EndDrawing(), automatic events polling
2192 void DisableEventWaiting(void)
2193 {
2194 CORE.Window.eventWaiting = false;
2195 }
2196
2197 // Show mouse cursor
2198 void ShowCursor(void)
2199 {
2200 #if defined(PLATFORM_DESKTOP)
2201 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
2202 #endif
2203
2204 CORE.Input.Mouse.cursorHidden = false;
2205 }
2206
2207 // Hides mouse cursor
2208 void HideCursor(void)
2209 {
2210 #if defined(PLATFORM_DESKTOP)
2211 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
2212 #endif
2213
2214 CORE.Input.Mouse.cursorHidden = true;
2215 }
2216
2217 // Check if cursor is not visible
2218 bool IsCursorHidden(void)
2219 {
2220 return CORE.Input.Mouse.cursorHidden;
2221 }
2222
2223 // Enables cursor (unlock cursor)
2224 void EnableCursor(void)
2225 {
2226 #if defined(PLATFORM_DESKTOP)
2227 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
2228 #endif
2229 #if defined(PLATFORM_WEB)
2230 emscripten_exit_pointerlock();
2231 #endif
2232 // Set cursor position in the middle
2233 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
2234
2235 CORE.Input.Mouse.cursorHidden = false;
2236 }
2237
2238 // Disables cursor (lock cursor)
2239 void DisableCursor(void)
2240 {
2241 #if defined(PLATFORM_DESKTOP)
2242 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
2243 #endif
2244 #if defined(PLATFORM_WEB)
2245 emscripten_request_pointerlock("#canvas", 1);
2246 #endif
2247 // Set cursor position in the middle
2248 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
2249
2250 CORE.Input.Mouse.cursorHidden = true;
2251 }
2252
2253 // Check if cursor is on the current screen.
2254 bool IsCursorOnScreen(void)
2255 {
2256 return CORE.Input.Mouse.cursorOnScreen;
2257 }
2258
2259 // Set background color (framebuffer clear color)
2260 void ClearBackground(Color color)
2261 {
2262 rlClearColor(color.r, color.g, color.b, color.a); // Set clear color
2263 rlClearScreenBuffers(); // Clear current framebuffers
2264 }
2265
2266 // Setup canvas (framebuffer) to start drawing
2267 void BeginDrawing(void)
2268 {
2269 // WARNING: Previously to BeginDrawing() other render textures drawing could happen,
2270 // consequently the measure for update vs draw is not accurate (only the total frame time is accurate)
2271
2272 CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer()
2273 CORE.Time.update = CORE.Time.current - CORE.Time.previous;
2274 CORE.Time.previous = CORE.Time.current;
2275
2276 rlLoadIdentity(); // Reset current matrix (modelview)
2277 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling
2278
2279 //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1
2280 // NOTE: Not required with OpenGL 3.3+
2281 }
2282
2283 // End canvas drawing and swap buffers (double buffering)
2284 void EndDrawing(void)
2285 {
2286 rlDrawRenderBatchActive(); // Update and draw internal render batch
2287
2288 #if defined(SUPPORT_GIF_RECORDING)
2289 // Draw record indicator
2290 if (gifRecording)
2291 {
2292 #define GIF_RECORD_FRAMERATE 10
2293 gifFrameCounter++;
2294
2295 // NOTE: We record one gif frame every 10 game frames
2296 if ((gifFrameCounter%GIF_RECORD_FRAMERATE) == 0)
2297 {
2298 // Get image data for the current frame (from backbuffer)
2299 // NOTE: This process is quite slow... :(
2300 Vector2 scale = GetWindowScaleDPI();
2301 unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y));
2302 msf_gif_frame(&gifState, screenData, 10, 16, (int)((float)CORE.Window.render.width*scale.x)*4);
2303
2304 RL_FREE(screenData); // Free image data
2305 }
2306
2307 #if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT)
2308 if (((gifFrameCounter/15)%2) == 1)
2309 {
2310 DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes
2311 DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext
2312 }
2313 #endif
2314
2315 rlDrawRenderBatchActive(); // Update and draw internal render batch
2316 }
2317 #endif
2318
2319 #if defined(SUPPORT_EVENTS_AUTOMATION)
2320 // Draw record/play indicator
2321 if (eventsRecording)
2322 {
2323 gifFrameCounter++;
2324
2325 if (((gifFrameCounter/15)%2) == 1)
2326 {
2327 DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON);
2328 DrawText("EVENTS RECORDING", 50, CORE.Window.screen.height - 25, 10, RED);
2329 }
2330
2331 rlDrawRenderBatchActive(); // Update and draw internal render batch
2332 }
2333 else if (eventsPlaying)
2334 {
2335 gifFrameCounter++;
2336
2337 if (((gifFrameCounter/15)%2) == 1)
2338 {
2339 DrawCircle(30, CORE.Window.screen.height - 20, 10, LIME);
2340 DrawText("EVENTS PLAYING", 50, CORE.Window.screen.height - 25, 10, GREEN);
2341 }
2342
2343 rlDrawRenderBatchActive(); // Update and draw internal render batch
2344 }
2345 #endif
2346
2347 #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL)
2348 SwapScreenBuffer(); // Copy back buffer to front buffer (screen)
2349
2350 // Frame time control system
2351 CORE.Time.current = GetTime();
2352 CORE.Time.draw = CORE.Time.current - CORE.Time.previous;
2353 CORE.Time.previous = CORE.Time.current;
2354
2355 CORE.Time.frame = CORE.Time.update + CORE.Time.draw;
2356
2357 // Wait for some milliseconds...
2358 if (CORE.Time.frame < CORE.Time.target)
2359 {
2360 WaitTime(CORE.Time.target - CORE.Time.frame);
2361
2362 CORE.Time.current = GetTime();
2363 double waitTime = CORE.Time.current - CORE.Time.previous;
2364 CORE.Time.previous = CORE.Time.current;
2365
2366 CORE.Time.frame += waitTime; // Total frame time: update + draw + wait
2367 }
2368
2369 PollInputEvents(); // Poll user events (before next frame update)
2370 #endif
2371
2372 #if defined(SUPPORT_EVENTS_AUTOMATION)
2373 // Events recording and playing logic
2374 if (eventsRecording) RecordAutomationEvent(CORE.Time.frameCounter);
2375 else if (eventsPlaying)
2376 {
2377 // TODO: When should we play? After/before/replace PollInputEvents()?
2378 if (CORE.Time.frameCounter >= eventCount) eventsPlaying = false;
2379 PlayAutomationEvent(CORE.Time.frameCounter);
2380 }
2381 #endif
2382
2383 CORE.Time.frameCounter++;
2384 }
2385
2386 // Initialize 2D mode with custom camera (2D)
2387 void BeginMode2D(Camera2D camera)
2388 {
2389 rlDrawRenderBatchActive(); // Update and draw internal render batch
2390
2391 rlLoadIdentity(); // Reset current matrix (modelview)
2392
2393 // Apply 2d camera transformation to modelview
2394 rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera)));
2395
2396 // Apply screen scaling if required
2397 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale));
2398 }
2399
2400 // Ends 2D mode with custom camera
2401 void EndMode2D(void)
2402 {
2403 rlDrawRenderBatchActive(); // Update and draw internal render batch
2404
2405 rlLoadIdentity(); // Reset current matrix (modelview)
2406 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required
2407 }
2408
2409 // Initializes 3D mode with custom camera (3D)
2410 void BeginMode3D(Camera camera)
2411 {
2412 rlDrawRenderBatchActive(); // Update and draw internal render batch
2413
2414 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
2415 rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection
2416 rlLoadIdentity(); // Reset current matrix (projection)
2417
2418 float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height;
2419
2420 // NOTE: zNear and zFar values are important when computing depth buffer values
2421 if (camera.projection == CAMERA_PERSPECTIVE)
2422 {
2423 // Setup perspective projection
2424 double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD);
2425 double right = top*aspect;
2426
2427 rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2428 }
2429 else if (camera.projection == CAMERA_ORTHOGRAPHIC)
2430 {
2431 // Setup orthographic projection
2432 double top = camera.fovy/2.0;
2433 double right = top*aspect;
2434
2435 rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2436 }
2437
2438 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
2439 rlLoadIdentity(); // Reset current matrix (modelview)
2440
2441 // Setup Camera view
2442 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
2443 rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera)
2444
2445 rlEnableDepthTest(); // Enable DEPTH_TEST for 3D
2446 }
2447
2448 // Ends 3D mode and returns to default 2D orthographic mode
2449 void EndMode3D(void)
2450 {
2451 rlDrawRenderBatchActive(); // Update and draw internal render batch
2452
2453 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
2454 rlPopMatrix(); // Restore previous matrix (projection) from matrix stack
2455
2456 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
2457 rlLoadIdentity(); // Reset current matrix (modelview)
2458
2459 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required
2460
2461 rlDisableDepthTest(); // Disable DEPTH_TEST for 2D
2462 }
2463
2464 // Initializes render texture for drawing
2465 void BeginTextureMode(RenderTexture2D target)
2466 {
2467 rlDrawRenderBatchActive(); // Update and draw internal render batch
2468
2469 rlEnableFramebuffer(target.id); // Enable render target
2470
2471 // Set viewport and RLGL internal framebuffer size
2472 rlViewport(0, 0, target.texture.width, target.texture.height);
2473 rlSetFramebufferWidth(target.texture.width);
2474 rlSetFramebufferHeight(target.texture.height);
2475
2476 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
2477 rlLoadIdentity(); // Reset current matrix (projection)
2478
2479 // Set orthographic projection to current framebuffer size
2480 // NOTE: Configured top-left corner as (0, 0)
2481 rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f);
2482
2483 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
2484 rlLoadIdentity(); // Reset current matrix (modelview)
2485
2486 //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?)
2487
2488 // Setup current width/height for proper aspect ratio
2489 // calculation when using BeginMode3D()
2490 CORE.Window.currentFbo.width = target.texture.width;
2491 CORE.Window.currentFbo.height = target.texture.height;
2492 }
2493
2494 // Ends drawing to render texture
2495 void EndTextureMode(void)
2496 {
2497 rlDrawRenderBatchActive(); // Update and draw internal render batch
2498
2499 rlDisableFramebuffer(); // Disable render target (fbo)
2500
2501 // Set viewport to default framebuffer size
2502 SetupViewport(CORE.Window.render.width, CORE.Window.render.height);
2503
2504 // Reset current fbo to screen size
2505 CORE.Window.currentFbo.width = CORE.Window.render.width;
2506 CORE.Window.currentFbo.height = CORE.Window.render.height;
2507 }
2508
2509 // Begin custom shader mode
2510 void BeginShaderMode(Shader shader)
2511 {
2512 rlSetShader(shader.id, shader.locs);
2513 }
2514
2515 // End custom shader mode (returns to default shader)
2516 void EndShaderMode(void)
2517 {
2518 rlSetShader(rlGetShaderIdDefault(), rlGetShaderLocsDefault());
2519 }
2520
2521 // Begin blending mode (alpha, additive, multiplied, subtract, custom)
2522 // NOTE: Blend modes supported are enumerated in BlendMode enum
2523 void BeginBlendMode(int mode)
2524 {
2525 rlSetBlendMode(mode);
2526 }
2527
2528 // End blending mode (reset to default: alpha blending)
2529 void EndBlendMode(void)
2530 {
2531 rlSetBlendMode(BLEND_ALPHA);
2532 }
2533
2534 // Begin scissor mode (define screen area for following drawing)
2535 // NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left
2536 void BeginScissorMode(int x, int y, int width, int height)
2537 {
2538 rlDrawRenderBatchActive(); // Update and draw internal render batch
2539
2540 rlEnableScissorTest();
2541
2542 #if defined(__APPLE__)
2543 Vector2 scale = GetWindowScaleDPI();
2544 rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y));
2545 #else
2546 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
2547 {
2548 Vector2 scale = GetWindowScaleDPI();
2549 rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y));
2550 }
2551 else
2552 {
2553 rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height);
2554 }
2555 #endif
2556 }
2557
2558 // End scissor mode
2559 void EndScissorMode(void)
2560 {
2561 rlDrawRenderBatchActive(); // Update and draw internal render batch
2562 rlDisableScissorTest();
2563 }
2564
2565 // Begin VR drawing configuration
2566 void BeginVrStereoMode(VrStereoConfig config)
2567 {
2568 rlEnableStereoRender();
2569
2570 // Set stereo render matrices
2571 rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]);
2572 rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]);
2573 }
2574
2575 // End VR drawing process (and desktop mirror)
2576 void EndVrStereoMode(void)
2577 {
2578 rlDisableStereoRender();
2579 }
2580
2581 // Load VR stereo config for VR simulator device parameters
2582 VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device)
2583 {
2584 VrStereoConfig config = { 0 };
2585
2586 if ((rlGetVersion() == RL_OPENGL_33) || (rlGetVersion() >= RL_OPENGL_ES_20))
2587 {
2588 // Compute aspect ratio
2589 float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution;
2590
2591 // Compute lens parameters
2592 float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize;
2593 config.leftLensCenter[0] = 0.25f + lensShift;
2594 config.leftLensCenter[1] = 0.5f;
2595 config.rightLensCenter[0] = 0.75f - lensShift;
2596 config.rightLensCenter[1] = 0.5f;
2597 config.leftScreenCenter[0] = 0.25f;
2598 config.leftScreenCenter[1] = 0.5f;
2599 config.rightScreenCenter[0] = 0.75f;
2600 config.rightScreenCenter[1] = 0.5f;
2601
2602 // Compute distortion scale parameters
2603 // NOTE: To get lens max radius, lensShift must be normalized to [-1..1]
2604 float lensRadius = fabsf(-1.0f - 4.0f*lensShift);
2605 float lensRadiusSq = lensRadius*lensRadius;
2606 float distortionScale = device.lensDistortionValues[0] +
2607 device.lensDistortionValues[1]*lensRadiusSq +
2608 device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq +
2609 device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq;
2610
2611 float normScreenWidth = 0.5f;
2612 float normScreenHeight = 1.0f;
2613 config.scaleIn[0] = 2.0f/normScreenWidth;
2614 config.scaleIn[1] = 2.0f/normScreenHeight/aspect;
2615 config.scale[0] = normScreenWidth*0.5f/distortionScale;
2616 config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale;
2617
2618 // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance)
2619 // ...but with lens distortion it is increased (see Oculus SDK Documentation)
2620 float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale?
2621 // float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance);
2622
2623 // Compute camera projection matrices
2624 float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1]
2625 Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2626
2627 config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f));
2628 config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f));
2629
2630 // Compute camera transformation matrices
2631 // NOTE: Camera movement might seem more natural if we model the head.
2632 // Our axis of rotation is the base of our head, so we might want to add
2633 // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions.
2634 config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f);
2635 config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f);
2636
2637 // Compute eyes Viewports
2638 /*
2639 config.eyeViewportRight[0] = 0;
2640 config.eyeViewportRight[1] = 0;
2641 config.eyeViewportRight[2] = device.hResolution/2;
2642 config.eyeViewportRight[3] = device.vResolution;
2643
2644 config.eyeViewportLeft[0] = device.hResolution/2;
2645 config.eyeViewportLeft[1] = 0;
2646 config.eyeViewportLeft[2] = device.hResolution/2;
2647 config.eyeViewportLeft[3] = device.vResolution;
2648 */
2649 }
2650 else TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1");
2651
2652 return config;
2653 }
2654
2655 // Unload VR stereo config properties
2656 void UnloadVrStereoConfig(VrStereoConfig config)
2657 {
2658 //...
2659 }
2660
2661 // Load shader from files and bind default locations
2662 // NOTE: If shader string is NULL, using default vertex/fragment shaders
2663 Shader LoadShader(const char *vsFileName, const char *fsFileName)
2664 {
2665 Shader shader = { 0 };
2666
2667 char *vShaderStr = NULL;
2668 char *fShaderStr = NULL;
2669
2670 if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName);
2671 if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName);
2672
2673 shader = LoadShaderFromMemory(vShaderStr, fShaderStr);
2674
2675 UnloadFileText(vShaderStr);
2676 UnloadFileText(fShaderStr);
2677
2678 return shader;
2679 }
2680
2681 // Load shader from code strings and bind default locations
2682 Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode)
2683 {
2684 Shader shader = { 0 };
2685
2686 shader.id = rlLoadShaderCode(vsCode, fsCode);
2687
2688 // After shader loading, we TRY to set default location names
2689 if (shader.id > 0)
2690 {
2691 // Default shader attribute locations have been binded before linking:
2692 // vertex position location = 0
2693 // vertex texcoord location = 1
2694 // vertex normal location = 2
2695 // vertex color location = 3
2696 // vertex tangent location = 4
2697 // vertex texcoord2 location = 5
2698
2699 // NOTE: If any location is not found, loc point becomes -1
2700
2701 shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int));
2702
2703 // All locations reset to -1 (no location)
2704 for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1;
2705
2706 // Get handles to GLSL input attribute locations
2707 shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION);
2708 shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD);
2709 shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2);
2710 shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL);
2711 shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT);
2712 shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR);
2713
2714 // Get handles to GLSL uniform locations (vertex shader)
2715 shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP);
2716 shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW);
2717 shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION);
2718 shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL);
2719 shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL);
2720
2721 // Get handles to GLSL uniform locations (fragment shader)
2722 shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR);
2723 shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); // SHADER_LOC_MAP_ALBEDO
2724 shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1); // SHADER_LOC_MAP_METALNESS
2725 shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2);
2726 }
2727
2728 return shader;
2729 }
2730
2731 // Check if a shader is ready
2732 bool IsShaderReady(Shader shader)
2733 {
2734 return ((shader.id > 0) && // Validate shader id (loaded successfully)
2735 (shader.locs != NULL)); // Validate memory has been allocated for default shader locations
2736
2737 // The following locations are tried to be set automatically (locs[i] >= 0),
2738 // any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION
2739 // NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones
2740
2741 // Vertex shader attribute locations (default)
2742 // shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader
2743 // shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader
2744 // shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]
2745 // shader.locs[SHADER_LOC_VERTEX_NORMAL]
2746 // shader.locs[SHADER_LOC_VERTEX_TANGENT]
2747 // shader.locs[SHADER_LOC_VERTEX_COLOR] // Set by default internal shader
2748
2749 // Vertex shader uniform locations (default)
2750 // shader.locs[SHADER_LOC_MATRIX_MVP] // Set by default internal shader
2751 // shader.locs[SHADER_LOC_MATRIX_VIEW]
2752 // shader.locs[SHADER_LOC_MATRIX_PROJECTION]
2753 // shader.locs[SHADER_LOC_MATRIX_MODEL]
2754 // shader.locs[SHADER_LOC_MATRIX_NORMAL]
2755
2756 // Fragment shader uniform locations (default)
2757 // shader.locs[SHADER_LOC_COLOR_DIFFUSE] // Set by default internal shader
2758 // shader.locs[SHADER_LOC_MAP_DIFFUSE] // Set by default internal shader
2759 // shader.locs[SHADER_LOC_MAP_SPECULAR]
2760 // shader.locs[SHADER_LOC_MAP_NORMAL]
2761 }
2762
2763 // Unload shader from GPU memory (VRAM)
2764 void UnloadShader(Shader shader)
2765 {
2766 if (shader.id != rlGetShaderIdDefault())
2767 {
2768 rlUnloadShaderProgram(shader.id);
2769
2770 // NOTE: If shader loading failed, it should be 0
2771 RL_FREE(shader.locs);
2772 }
2773 }
2774
2775 // Get shader uniform location
2776 int GetShaderLocation(Shader shader, const char *uniformName)
2777 {
2778 return rlGetLocationUniform(shader.id, uniformName);
2779 }
2780
2781 // Get shader attribute location
2782 int GetShaderLocationAttrib(Shader shader, const char *attribName)
2783 {
2784 return rlGetLocationAttrib(shader.id, attribName);
2785 }
2786
2787 // Set shader uniform value
2788 void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType)
2789 {
2790 SetShaderValueV(shader, locIndex, value, uniformType, 1);
2791 }
2792
2793 // Set shader uniform value vector
2794 void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count)
2795 {
2796 if (locIndex > -1)
2797 {
2798 rlEnableShader(shader.id);
2799 rlSetUniform(locIndex, value, uniformType, count);
2800 //rlDisableShader(); // Avoid resetting current shader program, in case other uniforms are set
2801 }
2802 }
2803
2804 // Set shader uniform value (matrix 4x4)
2805 void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat)
2806 {
2807 if (locIndex > -1)
2808 {
2809 rlEnableShader(shader.id);
2810 rlSetUniformMatrix(locIndex, mat);
2811 //rlDisableShader();
2812 }
2813 }
2814
2815 // Set shader uniform value for texture
2816 void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture)
2817 {
2818 if (locIndex > -1)
2819 {
2820 rlEnableShader(shader.id);
2821 rlSetUniformSampler(locIndex, texture.id);
2822 //rlDisableShader();
2823 }
2824 }
2825
2826 // Get a ray trace from mouse position
2827 Ray GetMouseRay(Vector2 mouse, Camera camera)
2828 {
2829 Ray ray = { 0 };
2830
2831 // Calculate normalized device coordinates
2832 // NOTE: y value is negative
2833 float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f;
2834 float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight();
2835 float z = 1.0f;
2836
2837 // Store values in a vector
2838 Vector3 deviceCoords = { x, y, z };
2839
2840 // Calculate view matrix from camera look at
2841 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
2842
2843 Matrix matProj = MatrixIdentity();
2844
2845 if (camera.projection == CAMERA_PERSPECTIVE)
2846 {
2847 // Calculate projection matrix from perspective
2848 matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2849 }
2850 else if (camera.projection == CAMERA_ORTHOGRAPHIC)
2851 {
2852 float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
2853 double top = camera.fovy/2.0;
2854 double right = top*aspect;
2855
2856 // Calculate projection matrix from orthographic
2857 matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0);
2858 }
2859
2860 // Unproject far/near points
2861 Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView);
2862 Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView);
2863
2864 // Unproject the mouse cursor in the near plane.
2865 // We need this as the source position because orthographic projects, compared to perspective doesn't have a
2866 // convergence point, meaning that the "eye" of the camera is more like a plane than a point.
2867 Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView);
2868
2869 // Calculate normalized direction vector
2870 Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint));
2871
2872 if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position;
2873 else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos;
2874
2875 // Apply calculated vectors to ray
2876 ray.direction = direction;
2877
2878 return ray;
2879 }
2880
2881 // Get transform matrix for camera
2882 Matrix GetCameraMatrix(Camera camera)
2883 {
2884 return MatrixLookAt(camera.position, camera.target, camera.up);
2885 }
2886
2887 // Get camera 2d transform matrix
2888 Matrix GetCameraMatrix2D(Camera2D camera)
2889 {
2890 Matrix matTransform = { 0 };
2891 // The camera in world-space is set by
2892 // 1. Move it to target
2893 // 2. Rotate by -rotation and scale by (1/zoom)
2894 // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller),
2895 // not for the camera getting bigger, hence the invert. Same deal with rotation.
2896 // 3. Move it by (-offset);
2897 // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera)
2898 // we need to do it into opposite direction (inverse transform)
2899
2900 // Having camera transform in world-space, inverse of it gives the modelview transform.
2901 // Since (A*B*C)' = C'*B'*A', the modelview is
2902 // 1. Move to offset
2903 // 2. Rotate and Scale
2904 // 3. Move by -target
2905 Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
2906 Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
2907 Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
2908 Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f);
2909
2910 matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation);
2911
2912 return matTransform;
2913 }
2914
2915 // Get the screen space position from a 3d world space position
2916 Vector2 GetWorldToScreen(Vector3 position, Camera camera)
2917 {
2918 Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight());
2919
2920 return screenPosition;
2921 }
2922
2923 // Get size position for a 3d world space position (useful for texture drawing)
2924 Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height)
2925 {
2926 // Calculate projection matrix (from perspective instead of frustum
2927 Matrix matProj = MatrixIdentity();
2928
2929 if (camera.projection == CAMERA_PERSPECTIVE)
2930 {
2931 // Calculate projection matrix from perspective
2932 matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2933 }
2934 else if (camera.projection == CAMERA_ORTHOGRAPHIC)
2935 {
2936 float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
2937 double top = camera.fovy/2.0;
2938 double right = top*aspect;
2939
2940 // Calculate projection matrix from orthographic
2941 matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
2942 }
2943
2944 // Calculate view matrix from camera look at (and transpose it)
2945 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
2946
2947 // TODO: Why not use Vector3Transform(Vector3 v, Matrix mat)?
2948
2949 // Convert world position vector to quaternion
2950 Quaternion worldPos = { position.x, position.y, position.z, 1.0f };
2951
2952 // Transform world position to view
2953 worldPos = QuaternionTransform(worldPos, matView);
2954
2955 // Transform result to projection (clip space position)
2956 worldPos = QuaternionTransform(worldPos, matProj);
2957
2958 // Calculate normalized device coordinates (inverted y)
2959 Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w };
2960
2961 // Calculate 2d screen position vector
2962 Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height };
2963
2964 return screenPosition;
2965 }
2966
2967 // Get the screen space position for a 2d camera world space position
2968 Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera)
2969 {
2970 Matrix matCamera = GetCameraMatrix2D(camera);
2971 Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera);
2972
2973 return (Vector2){ transform.x, transform.y };
2974 }
2975
2976 // Get the world space position for a 2d camera screen space position
2977 Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera)
2978 {
2979 Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera));
2980 Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera);
2981
2982 return (Vector2){ transform.x, transform.y };
2983 }
2984
2985 // Set target FPS (maximum)
2986 void SetTargetFPS(int fps)
2987 {
2988 if (fps < 1) CORE.Time.target = 0.0;
2989 else CORE.Time.target = 1.0/(double)fps;
2990
2991 TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f);
2992 }
2993
2994 // Get current FPS
2995 // NOTE: We calculate an average framerate
2996 int GetFPS(void)
2997 {
2998 int fps = 0;
2999
3000 #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL)
3001 #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures
3002 #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 milliseconds
3003 #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT)
3004
3005 static int index = 0;
3006 static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 };
3007 static float average = 0, last = 0;
3008 float fpsFrame = GetFrameTime();
3009
3010 if (fpsFrame == 0) return 0;
3011
3012 if ((GetTime() - last) > FPS_STEP)
3013 {
3014 last = (float)GetTime();
3015 index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT;
3016 average -= history[index];
3017 history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT;
3018 average += history[index];
3019 }
3020
3021 fps = (int)roundf(1.0f/average);
3022 #endif
3023
3024 return fps;
3025 }
3026
3027 // Get time in seconds for last frame drawn (delta time)
3028 float GetFrameTime(void)
3029 {
3030 return (float)CORE.Time.frame;
3031 }
3032
3033 // Get elapsed time measure in seconds since InitTimer()
3034 // NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow()
3035 // NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit()
3036 double GetTime(void)
3037 {
3038 double time = 0.0;
3039 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
3040 time = glfwGetTime(); // Elapsed time since glfwInit()
3041 #endif
3042
3043 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
3044 struct timespec ts = { 0 };
3045 clock_gettime(CLOCK_MONOTONIC, &ts);
3046 unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
3047
3048 time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer()
3049 #endif
3050 return time;
3051 }
3052
3053 // Setup window configuration flags (view FLAGS)
3054 // NOTE: This function is expected to be called before window creation,
3055 // because it sets up some flags for the window creation process.
3056 // To configure window states after creation, just use SetWindowState()
3057 void SetConfigFlags(unsigned int flags)
3058 {
3059 // Selected flags are set but not evaluated at this point,
3060 // flag evaluation happens at InitWindow() or SetWindowState()
3061 CORE.Window.flags |= flags;
3062 }
3063
3064 // NOTE TRACELOG() function is located in [utils.h]
3065
3066 // Takes a screenshot of current screen (saved a .png)
3067 void TakeScreenshot(const char *fileName)
3068 {
3069 #if defined(SUPPORT_MODULE_RTEXTURES)
3070 // Security check to (partially) avoid malicious code on PLATFORM_WEB
3071 if (strchr(fileName, '\'') != NULL) { TRACELOG(LOG_WARNING, "SYSTEM: Provided fileName could be potentially malicious, avoid [\'] character"); return; }
3072
3073 Vector2 scale = GetWindowScaleDPI();
3074 unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y));
3075 Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
3076
3077 char path[2048] = { 0 };
3078 strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName));
3079
3080 ExportImage(image, path); // WARNING: Module required: rtextures
3081 RL_FREE(imgData);
3082
3083 #if defined(PLATFORM_WEB)
3084 // Download file from MEMFS (emscripten memory filesystem)
3085 // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html
3086 emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path)));
3087 #endif
3088
3089 TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path);
3090 #else
3091 TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures");
3092 #endif
3093 }
3094
3095 // Get a random value between min and max (both included)
3096 // WARNING: Ranges higher than RAND_MAX will return invalid results
3097 // More specifically, if (max - min) > INT_MAX there will be an overflow,
3098 // and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold
3099 int GetRandomValue(int min, int max)
3100 {
3101 if (min > max)
3102 {
3103 int tmp = max;
3104 max = min;
3105 min = tmp;
3106 }
3107
3108 if ((unsigned int)(max - min) > (unsigned int)RAND_MAX)
3109 {
3110 TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX);
3111 }
3112
3113 return (rand()%(abs(max - min) + 1) + min);
3114 }
3115
3116 // Set the seed for the random number generator
3117 void SetRandomSeed(unsigned int seed)
3118 {
3119 srand(seed);
3120 }
3121
3122 // Check if the file exists
3123 bool FileExists(const char *fileName)
3124 {
3125 bool result = false;
3126
3127 #if defined(_WIN32)
3128 if (_access(fileName, 0) != -1) result = true;
3129 #else
3130 if (access(fileName, F_OK) != -1) result = true;
3131 #endif
3132
3133 // NOTE: Alternatively, stat() can be used instead of access()
3134 //#include <sys/stat.h>
3135 //struct stat statbuf;
3136 //if (stat(filename, &statbuf) == 0) result = true;
3137
3138 return result;
3139 }
3140
3141 // Check file extension
3142 // NOTE: Extensions checking is not case-sensitive
3143 bool IsFileExtension(const char *fileName, const char *ext)
3144 {
3145 #define MAX_FILE_EXTENSION_SIZE 16
3146
3147 bool result = false;
3148 const char *fileExt = GetFileExtension(fileName);
3149
3150 if (fileExt != NULL)
3151 {
3152 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_TEXT_MANIPULATION)
3153 int extCount = 0;
3154 const char **checkExts = TextSplit(ext, ';', &extCount); // WARNING: Module required: rtext
3155
3156 char fileExtLower[MAX_FILE_EXTENSION_SIZE + 1] = { 0 };
3157 strncpy(fileExtLower, TextToLower(fileExt), MAX_FILE_EXTENSION_SIZE); // WARNING: Module required: rtext
3158
3159 for (int i = 0; i < extCount; i++)
3160 {
3161 if (strcmp(fileExtLower, TextToLower(checkExts[i])) == 0)
3162 {
3163 result = true;
3164 break;
3165 }
3166 }
3167 #else
3168 if (strcmp(fileExt, ext) == 0) result = true;
3169 #endif
3170 }
3171
3172 return result;
3173 }
3174
3175 // Check if a directory path exists
3176 bool DirectoryExists(const char *dirPath)
3177 {
3178 bool result = false;
3179 DIR *dir = opendir(dirPath);
3180
3181 if (dir != NULL)
3182 {
3183 result = true;
3184 closedir(dir);
3185 }
3186
3187 return result;
3188 }
3189
3190 // Get file length in bytes
3191 // NOTE: GetFileSize() conflicts with windows.h
3192 int GetFileLength(const char *fileName)
3193 {
3194 int size = 0;
3195
3196 FILE *file = fopen(fileName, "rb");
3197
3198 if (file != NULL)
3199 {
3200 fseek(file, 0L, SEEK_END);
3201 size = (int)ftell(file);
3202 fclose(file);
3203 }
3204
3205 return size;
3206 }
3207
3208 // Get pointer to extension for a filename string (includes the dot: .png)
3209 const char *GetFileExtension(const char *fileName)
3210 {
3211 const char *dot = strrchr(fileName, '.');
3212
3213 if (!dot || dot == fileName) return NULL;
3214
3215 return dot;
3216 }
3217
3218 // String pointer reverse break: returns right-most occurrence of charset in s
3219 static const char *strprbrk(const char *s, const char *charset)
3220 {
3221 const char *latestMatch = NULL;
3222 for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { }
3223 return latestMatch;
3224 }
3225
3226 // Get pointer to filename for a path string
3227 const char *GetFileName(const char *filePath)
3228 {
3229 const char *fileName = NULL;
3230 if (filePath != NULL) fileName = strprbrk(filePath, "\\/");
3231
3232 if (!fileName) return filePath;
3233
3234 return fileName + 1;
3235 }
3236
3237 // Get filename string without extension (uses static string)
3238 const char *GetFileNameWithoutExt(const char *filePath)
3239 {
3240 #define MAX_FILENAMEWITHOUTEXT_LENGTH 256
3241
3242 static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 };
3243 memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH);
3244
3245 if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension
3246
3247 int size = (int)strlen(fileName); // Get size in bytes
3248
3249 for (int i = 0; (i < size) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++)
3250 {
3251 if (fileName[i] == '.')
3252 {
3253 // NOTE: We break on first '.' found
3254 fileName[i] = '\0';
3255 break;
3256 }
3257 }
3258
3259 return fileName;
3260 }
3261
3262 // Get directory for a given filePath
3263 const char *GetDirectoryPath(const char *filePath)
3264 {
3265 /*
3266 // NOTE: Directory separator is different in Windows and other platforms,
3267 // fortunately, Windows also support the '/' separator, that's the one should be used
3268 #if defined(_WIN32)
3269 char separator = '\\';
3270 #else
3271 char separator = '/';
3272 #endif
3273 */
3274 const char *lastSlash = NULL;
3275 static char dirPath[MAX_FILEPATH_LENGTH] = { 0 };
3276 memset(dirPath, 0, MAX_FILEPATH_LENGTH);
3277
3278 // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /),
3279 // we add the current directory path to dirPath
3280 if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/')
3281 {
3282 // For security, we set starting path to current directory,
3283 // obtained path will be concatenated to this
3284 dirPath[0] = '.';
3285 dirPath[1] = '/';
3286 }
3287
3288 lastSlash = strprbrk(filePath, "\\/");
3289 if (lastSlash)
3290 {
3291 if (lastSlash == filePath)
3292 {
3293 // The last and only slash is the leading one: path is in a root directory
3294 dirPath[0] = filePath[0];
3295 dirPath[1] = '\0';
3296 }
3297 else
3298 {
3299 // NOTE: Be careful, strncpy() is not safe, it does not care about '\0'
3300 memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1));
3301 dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually
3302 }
3303 }
3304
3305 return dirPath;
3306 }
3307
3308 // Get previous directory path for a given path
3309 const char *GetPrevDirectoryPath(const char *dirPath)
3310 {
3311 static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 };
3312 memset(prevDirPath, 0, MAX_FILEPATH_LENGTH);
3313 int pathLen = (int)strlen(dirPath);
3314
3315 if (pathLen <= 3) strcpy(prevDirPath, dirPath);
3316
3317 for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--)
3318 {
3319 if ((dirPath[i] == '\\') || (dirPath[i] == '/'))
3320 {
3321 // Check for root: "C:\" or "/"
3322 if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++;
3323
3324 strncpy(prevDirPath, dirPath, i);
3325 break;
3326 }
3327 }
3328
3329 return prevDirPath;
3330 }
3331
3332 // Get current working directory
3333 const char *GetWorkingDirectory(void)
3334 {
3335 static char currentDir[MAX_FILEPATH_LENGTH] = { 0 };
3336 memset(currentDir, 0, MAX_FILEPATH_LENGTH);
3337
3338 char *path = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1);
3339
3340 return path;
3341 }
3342
3343 const char *GetApplicationDirectory(void)
3344 {
3345 static char appDir[MAX_FILEPATH_LENGTH] = { 0 };
3346 memset(appDir, 0, MAX_FILEPATH_LENGTH);
3347
3348 #if defined(_WIN32)
3349 int len = 0;
3350 #if defined(UNICODE)
3351 unsigned short widePath[MAX_PATH];
3352 len = GetModuleFileNameW(NULL, widePath, MAX_PATH);
3353 len = WideCharToMultiByte(0, 0, widePath, len, appDir, MAX_PATH, NULL, NULL);
3354 #else
3355 len = GetModuleFileNameA(NULL, appDir, MAX_PATH);
3356 #endif
3357 if (len > 0)
3358 {
3359 for (int i = len; i >= 0; --i)
3360 {
3361 if (appDir[i] == '\\')
3362 {
3363 appDir[i + 1] = '\0';
3364 break;
3365 }
3366 }
3367 }
3368 else
3369 {
3370 appDir[0] = '.';
3371 appDir[1] = '\\';
3372 }
3373
3374 #elif defined(__linux__)
3375 unsigned int size = sizeof(appDir);
3376 ssize_t len = readlink("/proc/self/exe", appDir, size);
3377
3378 if (len > 0)
3379 {
3380 for (int i = len; i >= 0; --i)
3381 {
3382 if (appDir[i] == '/')
3383 {
3384 appDir[i + 1] = '\0';
3385 break;
3386 }
3387 }
3388 }
3389 else
3390 {
3391 appDir[0] = '.';
3392 appDir[1] = '/';
3393 }
3394 #elif defined(__APPLE__)
3395 uint32_t size = sizeof(appDir);
3396
3397 if (_NSGetExecutablePath(appDir, &size) == 0)
3398 {
3399 int len = strlen(appDir);
3400 for (int i = len; i >= 0; --i)
3401 {
3402 if (appDir[i] == '/')
3403 {
3404 appDir[i + 1] = '\0';
3405 break;
3406 }
3407 }
3408 }
3409 else
3410 {
3411 appDir[0] = '.';
3412 appDir[1] = '/';
3413 }
3414 #endif
3415
3416 return appDir;
3417 }
3418
3419 // Load directory filepaths
3420 // NOTE: Base path is prepended to the scanned filepaths
3421 // WARNING: Directory is scanned twice, first time to get files count
3422 // No recursive scanning is done!
3423 FilePathList LoadDirectoryFiles(const char *dirPath)
3424 {
3425 FilePathList files = { 0 };
3426 unsigned int fileCounter = 0;
3427
3428 struct dirent *entity;
3429 DIR *dir = opendir(dirPath);
3430
3431 if (dir != NULL) // It's a directory
3432 {
3433 // SCAN 1: Count files
3434 while ((entity = readdir(dir)) != NULL)
3435 {
3436 // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths
3437 if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++;
3438 }
3439
3440 // Memory allocation for dirFileCount
3441 files.capacity = fileCounter;
3442 files.paths = (char **)RL_MALLOC(files.capacity*sizeof(char *));
3443 for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char));
3444
3445 closedir(dir);
3446
3447 // SCAN 2: Read filepaths
3448 // NOTE: Directory paths are also registered
3449 ScanDirectoryFiles(dirPath, &files, NULL);
3450
3451 // Security check: read files.count should match fileCounter
3452 if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated");
3453 }
3454 else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file...
3455
3456 return files;
3457 }
3458
3459 // Load directory filepaths with extension filtering and recursive directory scan
3460 // NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY
3461 FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs)
3462 {
3463 FilePathList files = { 0 };
3464
3465 files.capacity = MAX_FILEPATH_CAPACITY;
3466 files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *));
3467 for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char));
3468
3469 // WARNING: basePath is always prepended to scanned paths
3470 if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter);
3471 else ScanDirectoryFiles(basePath, &files, filter);
3472
3473 return files;
3474 }
3475
3476 // Unload directory filepaths
3477 // WARNING: files.count is not reseted to 0 after unloading
3478 void UnloadDirectoryFiles(FilePathList files)
3479 {
3480 for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]);
3481
3482 RL_FREE(files.paths);
3483 }
3484
3485 // Change working directory, returns true on success
3486 bool ChangeDirectory(const char *dir)
3487 {
3488 bool result = CHDIR(dir);
3489
3490 if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir);
3491
3492 return (result == 0);
3493 }
3494
3495 // Check if a given path point to a file
3496 bool IsPathFile(const char *path)
3497 {
3498 struct stat pathStat = { 0 };
3499 stat(path, &pathStat);
3500
3501 return S_ISREG(pathStat.st_mode);
3502 }
3503
3504 // Check if a file has been dropped into window
3505 bool IsFileDropped(void)
3506 {
3507 if (CORE.Window.dropFileCount > 0) return true;
3508 else return false;
3509 }
3510
3511 // Load dropped filepaths
3512 FilePathList LoadDroppedFiles(void)
3513 {
3514 FilePathList files = { 0 };
3515
3516 files.count = CORE.Window.dropFileCount;
3517 files.paths = CORE.Window.dropFilepaths;
3518
3519 return files;
3520 }
3521
3522 // Unload dropped filepaths
3523 void UnloadDroppedFiles(FilePathList files)
3524 {
3525 // WARNING: files pointers are the same as internal ones
3526
3527 if (files.count > 0)
3528 {
3529 for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]);
3530
3531 RL_FREE(files.paths);
3532
3533 CORE.Window.dropFileCount = 0;
3534 CORE.Window.dropFilepaths = NULL;
3535 }
3536 }
3537
3538 // Get file modification time (last write time)
3539 long GetFileModTime(const char *fileName)
3540 {
3541 struct stat result = { 0 };
3542
3543 if (stat(fileName, &result) == 0)
3544 {
3545 time_t mod = result.st_mtime;
3546
3547 return (long)mod;
3548 }
3549
3550 return 0;
3551 }
3552
3553 // Compress data (DEFLATE algorithm)
3554 unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize)
3555 {
3556 #define COMPRESSION_QUALITY_DEFLATE 8
3557
3558 unsigned char *compData = NULL;
3559
3560 #if defined(SUPPORT_COMPRESSION_API)
3561 // Compress data and generate a valid DEFLATE stream
3562 struct sdefl sdefl = { 0 };
3563 int bounds = sdefl_bound(dataSize);
3564 compData = (unsigned char *)RL_CALLOC(bounds, 1);
3565 *compDataSize = sdeflate(&sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbiw
3566
3567 TRACELOG(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize);
3568 #endif
3569
3570 return compData;
3571 }
3572
3573 // Decompress data (DEFLATE algorithm)
3574 unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize)
3575 {
3576 unsigned char *data = NULL;
3577
3578 #if defined(SUPPORT_COMPRESSION_API)
3579 // Decompress data from a valid DEFLATE stream
3580 data = (unsigned char *)RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1);
3581 int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize);
3582
3583 // WARNING: RL_REALLOC can make (and leave) data copies in memory, be careful with sensitive compressed data!
3584 // TODO: Use a different approach, create another buffer, copy data manually to it and wipe original buffer memory
3585 unsigned char *temp = (unsigned char *)RL_REALLOC(data, length);
3586
3587 if (temp != NULL) data = temp;
3588 else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory");
3589
3590 *dataSize = length;
3591
3592 TRACELOG(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, *dataSize);
3593 #endif
3594
3595 return data;
3596 }
3597
3598 // Encode data to Base64 string
3599 char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize)
3600 {
3601 static const unsigned char base64encodeTable[] = {
3602 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
3603 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
3604 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3605 };
3606
3607 static const int modTable[] = { 0, 2, 1 };
3608
3609 *outputSize = 4*((dataSize + 2)/3);
3610
3611 char *encodedData = (char *)RL_MALLOC(*outputSize);
3612
3613 if (encodedData == NULL) return NULL;
3614
3615 for (int i = 0, j = 0; i < dataSize;)
3616 {
3617 unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0;
3618 unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0;
3619 unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0;
3620
3621 unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC;
3622
3623 encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F];
3624 encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F];
3625 encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F];
3626 encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F];
3627 }
3628
3629 for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character
3630
3631 return encodedData;
3632 }
3633
3634 // Decode Base64 string data
3635 unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize)
3636 {
3637 static const unsigned char base64decodeTable[] = {
3638 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3639 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
3640 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
3641 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
3642 };
3643
3644 // Get output size of Base64 input data
3645 int outSize = 0;
3646 for (int i = 0; data[4*i] != 0; i++)
3647 {
3648 if (data[4*i + 3] == '=')
3649 {
3650 if (data[4*i + 2] == '=') outSize += 1;
3651 else outSize += 2;
3652 }
3653 else outSize += 3;
3654 }
3655
3656 // Allocate memory to store decoded Base64 data
3657 unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize);
3658
3659 for (int i = 0; i < outSize/3; i++)
3660 {
3661 unsigned char a = base64decodeTable[(int)data[4*i]];
3662 unsigned char b = base64decodeTable[(int)data[4*i + 1]];
3663 unsigned char c = base64decodeTable[(int)data[4*i + 2]];
3664 unsigned char d = base64decodeTable[(int)data[4*i + 3]];
3665
3666 decodedData[3*i] = (a << 2) | (b >> 4);
3667 decodedData[3*i + 1] = (b << 4) | (c >> 2);
3668 decodedData[3*i + 2] = (c << 6) | d;
3669 }
3670
3671 if (outSize%3 == 1)
3672 {
3673 int n = outSize/3;
3674 unsigned char a = base64decodeTable[(int)data[4*n]];
3675 unsigned char b = base64decodeTable[(int)data[4*n + 1]];
3676 decodedData[outSize - 1] = (a << 2) | (b >> 4);
3677 }
3678 else if (outSize%3 == 2)
3679 {
3680 int n = outSize/3;
3681 unsigned char a = base64decodeTable[(int)data[4*n]];
3682 unsigned char b = base64decodeTable[(int)data[4*n + 1]];
3683 unsigned char c = base64decodeTable[(int)data[4*n + 2]];
3684 decodedData[outSize - 2] = (a << 2) | (b >> 4);
3685 decodedData[outSize - 1] = (b << 4) | (c >> 2);
3686 }
3687
3688 *outputSize = outSize;
3689 return decodedData;
3690 }
3691
3692 // Open URL with default system browser (if available)
3693 // NOTE: This function is only safe to use if you control the URL given.
3694 // A user could craft a malicious string performing another action.
3695 // Only call this function yourself not with user input or make sure to check the string yourself.
3696 // Ref: https://github.com/raysan5/raylib/issues/686
3697 void OpenURL(const char *url)
3698 {
3699 // Security check to (partially) avoid malicious code on PLATFORM_WEB
3700 if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character");
3701 else
3702 {
3703 #if defined(PLATFORM_DESKTOP)
3704 char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char));
3705 #if defined(_WIN32)
3706 sprintf(cmd, "explorer \"%s\"", url);
3707 #endif
3708 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
3709 sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser
3710 #endif
3711 #if defined(__APPLE__)
3712 sprintf(cmd, "open '%s'", url);
3713 #endif
3714 int result = system(cmd);
3715 if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created");
3716 RL_FREE(cmd);
3717 #endif
3718 #if defined(PLATFORM_WEB)
3719 emscripten_run_script(TextFormat("window.open('%s', '_blank')", url));
3720 #endif
3721 #if defined(PLATFORM_ANDROID)
3722 JNIEnv *env = NULL;
3723 JavaVM *vm = CORE.Android.app->activity->vm;
3724 (*vm)->AttachCurrentThread(vm, &env, NULL);
3725
3726 jstring urlString = (*env)->NewStringUTF(env, url);
3727 jclass uriClass = (*env)->FindClass(env, "android/net/Uri");
3728 jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
3729 jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString);
3730
3731 jclass intentClass = (*env)->FindClass(env, "android/content/Intent");
3732 jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;");
3733 jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId);
3734 jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V");
3735 jobject intent = (*env)->AllocObject(env, intentClass);
3736
3737 (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri);
3738 jclass activityClass = (*env)->FindClass(env, "android/app/Activity");
3739 jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V");
3740 (*env)->CallVoidMethod(env, CORE.Android.app->activity->clazz, startActivity, intent);
3741
3742 (*vm)->DetachCurrentThread(vm);
3743 #endif
3744 }
3745 }
3746
3747 //----------------------------------------------------------------------------------
3748 // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions
3749 //----------------------------------------------------------------------------------
3750 // Check if a key has been pressed once
3751 bool IsKeyPressed(int key)
3752 {
3753 if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false;
3754 bool pressed = false;
3755
3756 if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true;
3757
3758 return pressed;
3759 }
3760
3761 // Check if a key has been pressed again (only PLATFORM_DESKTOP)
3762 bool IsKeyPressedRepeat(int key)
3763 {
3764 if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false;
3765 if (CORE.Input.Keyboard.keyRepeatInFrame[key] == 1) return true;
3766 else return false;
3767 }
3768
3769 // Check if a key is being pressed (key held down)
3770 bool IsKeyDown(int key)
3771 {
3772 if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false;
3773 if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true;
3774 else return false;
3775 }
3776
3777 // Check if a key has been released once
3778 bool IsKeyReleased(int key)
3779 {
3780 if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false;
3781 bool released = false;
3782
3783 if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true;
3784
3785 return released;
3786 }
3787
3788 // Check if a key is NOT being pressed (key not held down)
3789 bool IsKeyUp(int key)
3790 {
3791 if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false;
3792 if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true;
3793 else return false;
3794 }
3795
3796 // Get the last key pressed
3797 int GetKeyPressed(void)
3798 {
3799 int value = 0;
3800
3801 if (CORE.Input.Keyboard.keyPressedQueueCount > 0)
3802 {
3803 // Get character from the queue head
3804 value = CORE.Input.Keyboard.keyPressedQueue[0];
3805
3806 // Shift elements 1 step toward the head.
3807 for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++)
3808 CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1];
3809
3810 // Reset last character in the queue
3811 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount - 1] = 0;
3812 CORE.Input.Keyboard.keyPressedQueueCount--;
3813 }
3814
3815 return value;
3816 }
3817
3818 // Get the last char pressed
3819 int GetCharPressed(void)
3820 {
3821 int value = 0;
3822
3823 if (CORE.Input.Keyboard.charPressedQueueCount > 0)
3824 {
3825 // Get character from the queue head
3826 value = CORE.Input.Keyboard.charPressedQueue[0];
3827
3828 // Shift elements 1 step toward the head.
3829 for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++)
3830 CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1];
3831
3832 // Reset last character in the queue
3833 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount - 1] = 0;
3834 CORE.Input.Keyboard.charPressedQueueCount--;
3835 }
3836
3837 return value;
3838 }
3839
3840 // Set a custom key to exit program
3841 // NOTE: default exitKey is ESCAPE
3842 void SetExitKey(int key)
3843 {
3844 #if !defined(PLATFORM_ANDROID)
3845 CORE.Input.Keyboard.exitKey = key;
3846 #endif
3847 }
3848
3849 // NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB)
3850
3851 // Check if a gamepad is available
3852 bool IsGamepadAvailable(int gamepad)
3853 {
3854 bool result = false;
3855
3856 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true;
3857
3858 return result;
3859 }
3860
3861 // Get gamepad internal name id
3862 const char *GetGamepadName(int gamepad)
3863 {
3864 #if defined(PLATFORM_DESKTOP)
3865 if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad);
3866 else return NULL;
3867 #endif
3868 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
3869 if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name[gamepad]);
3870 return CORE.Input.Gamepad.name[gamepad];
3871 #endif
3872 #if defined(PLATFORM_WEB)
3873 return CORE.Input.Gamepad.name[gamepad];
3874 #endif
3875 return NULL;
3876 }
3877
3878 // Get gamepad axis count
3879 int GetGamepadAxisCount(int gamepad)
3880 {
3881 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
3882 int axisCount = 0;
3883 if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount);
3884 CORE.Input.Gamepad.axisCount = axisCount;
3885 #endif
3886
3887 return CORE.Input.Gamepad.axisCount;
3888 }
3889
3890 // Get axis movement vector for a gamepad
3891 float GetGamepadAxisMovement(int gamepad, int axis)
3892 {
3893 float value = 0;
3894
3895 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) &&
3896 (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA
3897
3898 return value;
3899 }
3900
3901 // Check if a gamepad button has been pressed once
3902 bool IsGamepadButtonPressed(int gamepad, int button)
3903 {
3904 bool pressed = false;
3905
3906 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
3907 (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true;
3908
3909 return pressed;
3910 }
3911
3912 // Check if a gamepad button is being pressed
3913 bool IsGamepadButtonDown(int gamepad, int button)
3914 {
3915 bool result = false;
3916
3917 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
3918 (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) result = true;
3919
3920 return result;
3921 }
3922
3923 // Check if a gamepad button has NOT been pressed once
3924 bool IsGamepadButtonReleased(int gamepad, int button)
3925 {
3926 bool released = false;
3927
3928 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
3929 (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true;
3930
3931 return released;
3932 }
3933
3934 // Check if a gamepad button is NOT being pressed
3935 bool IsGamepadButtonUp(int gamepad, int button)
3936 {
3937 bool result = false;
3938
3939 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
3940 (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) result = true;
3941
3942 return result;
3943 }
3944
3945 // Get the last gamepad button pressed
3946 int GetGamepadButtonPressed(void)
3947 {
3948 return CORE.Input.Gamepad.lastButtonPressed;
3949 }
3950
3951 // Set internal gamepad mappings
3952 int SetGamepadMappings(const char *mappings)
3953 {
3954 int result = 0;
3955
3956 #if defined(PLATFORM_DESKTOP)
3957 result = glfwUpdateGamepadMappings(mappings);
3958 #endif
3959
3960 return result;
3961 }
3962
3963 // Check if a mouse button has been pressed once
3964 bool IsMouseButtonPressed(int button)
3965 {
3966 bool pressed = false;
3967
3968 if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true;
3969
3970 // Map touches to mouse buttons checking
3971 if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true;
3972
3973 return pressed;
3974 }
3975
3976 // Check if a mouse button is being pressed
3977 bool IsMouseButtonDown(int button)
3978 {
3979 bool down = false;
3980
3981 if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true;
3982
3983 // Map touches to mouse buttons checking
3984 if (CORE.Input.Touch.currentTouchState[button] == 1) down = true;
3985
3986 return down;
3987 }
3988
3989 // Check if a mouse button has been released once
3990 bool IsMouseButtonReleased(int button)
3991 {
3992 bool released = false;
3993
3994 if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true;
3995
3996 // Map touches to mouse buttons checking
3997 if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true;
3998
3999 return released;
4000 }
4001
4002 // Check if a mouse button is NOT being pressed
4003 bool IsMouseButtonUp(int button)
4004 {
4005 return !IsMouseButtonDown(button);
4006 }
4007
4008 // Get mouse position X
4009 int GetMouseX(void)
4010 {
4011 #if defined(PLATFORM_ANDROID)
4012 return (int)CORE.Input.Touch.position[0].x;
4013 #else
4014 return (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x);
4015 #endif
4016 }
4017
4018 // Get mouse position Y
4019 int GetMouseY(void)
4020 {
4021 #if defined(PLATFORM_ANDROID)
4022 return (int)CORE.Input.Touch.position[0].y;
4023 #else
4024 return (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y);
4025 #endif
4026 }
4027
4028 // Get mouse position XY
4029 Vector2 GetMousePosition(void)
4030 {
4031 Vector2 position = { 0 };
4032
4033 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
4034 position = GetTouchPosition(0);
4035 #else
4036 position.x = (CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x;
4037 position.y = (CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y;
4038 #endif
4039
4040 return position;
4041 }
4042
4043 // Get mouse delta between frames
4044 Vector2 GetMouseDelta(void)
4045 {
4046 Vector2 delta = { 0 };
4047
4048 delta.x = CORE.Input.Mouse.currentPosition.x - CORE.Input.Mouse.previousPosition.x;
4049 delta.y = CORE.Input.Mouse.currentPosition.y - CORE.Input.Mouse.previousPosition.y;
4050
4051 return delta;
4052 }
4053
4054 // Set mouse position XY
4055 void SetMousePosition(int x, int y)
4056 {
4057 CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
4058 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
4059
4060 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
4061 // NOTE: emscripten not implemented
4062 glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y);
4063 #endif
4064 }
4065
4066 // Set mouse offset
4067 // NOTE: Useful when rendering to different size targets
4068 void SetMouseOffset(int offsetX, int offsetY)
4069 {
4070 CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY };
4071 }
4072
4073 // Set mouse scaling
4074 // NOTE: Useful when rendering to different size targets
4075 void SetMouseScale(float scaleX, float scaleY)
4076 {
4077 CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY };
4078 }
4079
4080 // Get mouse wheel movement Y
4081 float GetMouseWheelMove(void)
4082 {
4083 float result = 0.0f;
4084
4085 #if !defined(PLATFORM_ANDROID)
4086 if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x;
4087 else result = (float)CORE.Input.Mouse.currentWheelMove.y;
4088 #endif
4089
4090 return result;
4091 }
4092
4093 // Get mouse wheel movement X/Y as a vector
4094 Vector2 GetMouseWheelMoveV(void)
4095 {
4096 Vector2 result = { 0 };
4097
4098 result = CORE.Input.Mouse.currentWheelMove;
4099
4100 return result;
4101 }
4102
4103 // Set mouse cursor
4104 // NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP
4105 void SetMouseCursor(int cursor)
4106 {
4107 #if defined(PLATFORM_DESKTOP)
4108 CORE.Input.Mouse.cursor = cursor;
4109 if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL);
4110 else
4111 {
4112 // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values
4113 glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor));
4114 }
4115 #endif
4116 }
4117
4118 // Get touch position X for touch point 0 (relative to screen size)
4119 int GetTouchX(void)
4120 {
4121 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
4122 return (int)CORE.Input.Touch.position[0].x;
4123 #else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM
4124 return GetMouseX();
4125 #endif
4126 }
4127
4128 // Get touch position Y for touch point 0 (relative to screen size)
4129 int GetTouchY(void)
4130 {
4131 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
4132 return (int)CORE.Input.Touch.position[0].y;
4133 #else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM
4134 return GetMouseY();
4135 #endif
4136 }
4137
4138 // Get touch position XY for a touch point index (relative to screen size)
4139 // TODO: Touch position should be scaled depending on display size and render size
4140 Vector2 GetTouchPosition(int index)
4141 {
4142 Vector2 position = { -1.0f, -1.0f };
4143
4144 #if defined(PLATFORM_DESKTOP)
4145 // TODO: GLFW does not support multi-touch input just yet
4146 // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch
4147 // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages
4148 if (index == 0) position = GetMousePosition();
4149 #endif
4150 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
4151 if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index];
4152 else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS);
4153 #endif
4154
4155 return position;
4156 }
4157
4158 // Get touch point identifier for given index
4159 int GetTouchPointId(int index)
4160 {
4161 int id = -1;
4162
4163 if (index < MAX_TOUCH_POINTS) id = CORE.Input.Touch.pointId[index];
4164
4165 return id;
4166 }
4167
4168 // Get number of touch points
4169 int GetTouchPointCount(void)
4170 {
4171 return CORE.Input.Touch.pointCount;
4172 }
4173
4174 //----------------------------------------------------------------------------------
4175 // Module specific Functions Definition
4176 //----------------------------------------------------------------------------------
4177
4178 // Initialize display device and framebuffer
4179 // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size
4180 // If width or height are 0, default display size will be used for framebuffer size
4181 // NOTE: returns false in case graphic device could not be created
4182 static bool InitGraphicsDevice(int width, int height)
4183 {
4184 CORE.Window.screen.width = width; // User desired width
4185 CORE.Window.screen.height = height; // User desired height
4186 CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default
4187
4188 // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars...
4189 // ...in top-down or left-right to match display aspect ratio (no weird scaling)
4190
4191 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
4192 glfwSetErrorCallback(ErrorCallback);
4193 /*
4194 // TODO: Setup GLFW custom allocators to match raylib ones
4195 const GLFWallocator allocator = {
4196 .allocate = MemAlloc,
4197 .deallocate = MemFree,
4198 .reallocate = MemRealloc,
4199 .user = NULL
4200 };
4201
4202 glfwInitAllocator(&allocator);
4203 */
4204 #if defined(__APPLE__)
4205 glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE);
4206 #endif
4207
4208 if (!glfwInit())
4209 {
4210 TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW");
4211 return false;
4212 }
4213
4214 glfwDefaultWindowHints(); // Set default windows hints
4215 //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits
4216 //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits
4217 //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits
4218 //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits
4219 //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits
4220 //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window
4221 //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API
4222 //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers
4223
4224 // Check window creation flags
4225 if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true;
4226
4227 if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window
4228 else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden
4229
4230 if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window
4231 else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window
4232
4233 if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window
4234 else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable
4235
4236 // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization
4237 if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED;
4238
4239 // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization
4240 if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED;
4241
4242 if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
4243 else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
4244
4245 if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
4246 else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE);
4247
4248 // NOTE: Some GLFW flags are not supported on HTML5
4249 #if defined(PLATFORM_DESKTOP)
4250 if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer
4251 else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer
4252
4253 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
4254 {
4255 // Resize window content area based on the monitor content scale.
4256 // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11.
4257 // On platforms like macOS the resolution of the framebuffer is changed independently of the window size.
4258 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on
4259 #if defined(__APPLE__)
4260 glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
4261 #endif
4262 }
4263 else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE);
4264
4265 // Mouse passthrough
4266 if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE);
4267 else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE);
4268 #endif
4269
4270 if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
4271 {
4272 // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs
4273 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
4274 glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0
4275 }
4276
4277 // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version
4278 // with backward compatibility to older OpenGL versions.
4279 // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context.
4280
4281 // Check selection OpenGL version
4282 if (rlGetVersion() == RL_OPENGL_21)
4283 {
4284 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint)
4285 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint)
4286 }
4287 else if (rlGetVersion() == RL_OPENGL_33)
4288 {
4289 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint)
4290 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint)
4291 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above!
4292 // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE
4293 #if defined(__APPLE__)
4294 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires forward compatibility
4295 #else
4296 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above!
4297 #endif
4298 //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context
4299 }
4300 else if (rlGetVersion() == RL_OPENGL_43)
4301 {
4302 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint)
4303 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint)
4304 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
4305 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE);
4306 #if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT)
4307 glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context
4308 #endif
4309 }
4310 else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
4311 {
4312 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
4313 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
4314 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
4315 #if defined(PLATFORM_DESKTOP)
4316 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
4317 #else
4318 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
4319 #endif
4320 }
4321 else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
4322 {
4323 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
4324 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
4325 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
4326 #if defined(PLATFORM_DESKTOP)
4327 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
4328 #else
4329 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
4330 #endif
4331 }
4332
4333 #if defined(PLATFORM_DESKTOP)
4334 // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions.
4335 // Forcing this initialization here avoids doing it on PollInputEvents() called by EndDrawing() after first frame has been just drawn.
4336 // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience.
4337 // REF: https://github.com/raysan5/raylib/issues/1554
4338 if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL);
4339 #endif
4340
4341 #if defined(PLATFORM_DESKTOP)
4342 // Find monitor resolution
4343 GLFWmonitor *monitor = glfwGetPrimaryMonitor();
4344 if (!monitor)
4345 {
4346 TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor");
4347 return false;
4348 }
4349
4350 const GLFWvidmode *mode = glfwGetVideoMode(monitor);
4351
4352 CORE.Window.display.width = mode->width;
4353 CORE.Window.display.height = mode->height;
4354
4355 // Set screen width/height to the display width/height if they are 0
4356 if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width;
4357 if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height;
4358 #endif // PLATFORM_DESKTOP
4359
4360 #if defined(PLATFORM_WEB)
4361 // NOTE: Getting video modes is not implemented in emscripten GLFW3 version
4362 CORE.Window.display.width = CORE.Window.screen.width;
4363 CORE.Window.display.height = CORE.Window.screen.height;
4364 #endif // PLATFORM_WEB
4365
4366 if (CORE.Window.fullscreen)
4367 {
4368 // remember center for switchinging from fullscreen to window
4369 if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width))
4370 {
4371 // If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed.
4372 // Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11.
4373 CORE.Window.position.x = CORE.Window.display.width/4;
4374 CORE.Window.position.y = CORE.Window.display.height/4;
4375 }
4376 else
4377 {
4378 CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2;
4379 CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2;
4380 }
4381
4382 if (CORE.Window.position.x < 0) CORE.Window.position.x = 0;
4383 if (CORE.Window.position.y < 0) CORE.Window.position.y = 0;
4384
4385 // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor
4386 int count = 0;
4387 const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count);
4388
4389 // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height
4390 for (int i = 0; i < count; i++)
4391 {
4392 if ((unsigned int)modes[i].width >= CORE.Window.screen.width)
4393 {
4394 if ((unsigned int)modes[i].height >= CORE.Window.screen.height)
4395 {
4396 CORE.Window.display.width = modes[i].width;
4397 CORE.Window.display.height = modes[i].height;
4398 break;
4399 }
4400 }
4401 }
4402 TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
4403
4404 // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example,
4405 // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3),
4406 // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched
4407 // by the sides to fit all monitor space...
4408
4409 // Try to setup the most appropriate fullscreen framebuffer for the requested screenWidth/screenHeight
4410 // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset)
4411 // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale
4412 // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed...
4413 // HighDPI monitors are properly considered in a following similar function: SetupViewport()
4414 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
4415
4416 CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL);
4417
4418 // NOTE: Full-screen change, not working properly...
4419 //glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
4420 }
4421 else
4422 {
4423 #if defined(PLATFORM_DESKTOP)
4424 // If we are windowed fullscreen, ensures that window does not minimize when focus is lost
4425 if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width))
4426 {
4427 glfwWindowHint(GLFW_AUTO_ICONIFY, 0);
4428 }
4429 #endif
4430 // No-fullscreen window creation
4431 CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL);
4432
4433 if (CORE.Window.handle)
4434 {
4435 CORE.Window.render.width = CORE.Window.screen.width;
4436 CORE.Window.render.height = CORE.Window.screen.height;
4437 }
4438 }
4439
4440 if (!CORE.Window.handle)
4441 {
4442 glfwTerminate();
4443 TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window");
4444 return false;
4445 }
4446
4447 // glfwCreateWindow title doesn't work with emscripten.
4448 #if defined(PLATFORM_WEB)
4449 emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " ");
4450 #endif
4451
4452 // Set window callback events
4453 glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default!
4454 #if !defined(PLATFORM_WEB)
4455 glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback);
4456 #endif
4457 glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback);
4458 glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback);
4459 glfwSetDropCallback(CORE.Window.handle, WindowDropCallback);
4460
4461 // Set input callback events
4462 glfwSetKeyCallback(CORE.Window.handle, KeyCallback);
4463 glfwSetCharCallback(CORE.Window.handle, CharCallback);
4464 glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback);
4465 glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes
4466 glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback);
4467 glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback);
4468
4469 glfwMakeContextCurrent(CORE.Window.handle);
4470
4471 #if !defined(PLATFORM_WEB)
4472 glfwSetInputMode(CORE.Window.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM)
4473
4474 glfwSwapInterval(0); // No V-Sync by default
4475 #endif
4476
4477 // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS)
4478 // NOTE: V-Sync can be enabled by graphic driver configuration, it doesn't need
4479 // to be activated on web platforms since VSync is enforced there.
4480 #if !defined(PLATFORM_WEB)
4481 if (CORE.Window.flags & FLAG_VSYNC_HINT)
4482 {
4483 // WARNING: It seems to hit a critical render path in Intel HD Graphics
4484 glfwSwapInterval(1);
4485 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC");
4486 }
4487 #endif
4488
4489 int fbWidth = CORE.Window.screen.width;
4490 int fbHeight = CORE.Window.screen.height;
4491
4492 #if defined(PLATFORM_DESKTOP)
4493 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
4494 {
4495 // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling.
4496 // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
4497 #if !defined(__APPLE__)
4498 glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight);
4499
4500 // Screen scaling matrix is required in case desired screen area is different from display area
4501 CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f);
4502
4503 // Mouse input scaling for the new screen size
4504 SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight);
4505 #endif
4506 }
4507 #endif
4508
4509 CORE.Window.render.width = fbWidth;
4510 CORE.Window.render.height = fbHeight;
4511 CORE.Window.currentFbo.width = fbWidth;
4512 CORE.Window.currentFbo.height = fbHeight;
4513
4514 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
4515 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
4516 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
4517 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
4518 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
4519
4520 #endif // PLATFORM_DESKTOP || PLATFORM_WEB
4521
4522 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
4523 CORE.Window.fullscreen = true;
4524 CORE.Window.flags |= FLAG_FULLSCREEN_MODE;
4525
4526 #if defined(PLATFORM_RPI)
4527 bcm_host_init();
4528
4529 DISPMANX_ELEMENT_HANDLE_T dispmanElement = { 0 };
4530 DISPMANX_DISPLAY_HANDLE_T dispmanDisplay = { 0 };
4531 DISPMANX_UPDATE_HANDLE_T dispmanUpdate = { 0 };
4532
4533 VC_RECT_T dstRect = { 0 };
4534 VC_RECT_T srcRect = { 0 };
4535 #endif
4536
4537 #if defined(PLATFORM_DRM)
4538 CORE.Window.fd = -1;
4539 CORE.Window.connector = NULL;
4540 CORE.Window.modeIndex = -1;
4541 CORE.Window.crtc = NULL;
4542 CORE.Window.gbmDevice = NULL;
4543 CORE.Window.gbmSurface = NULL;
4544 CORE.Window.prevBO = NULL;
4545 CORE.Window.prevFB = 0;
4546
4547 #if defined(DEFAULT_GRAPHIC_DEVICE_DRM)
4548 CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR);
4549 #else
4550 TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying platform-gpu-card");
4551 CORE.Window.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4)
4552
4553 if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL))
4554 {
4555 TRACELOG(LOG_INFO, "DISPLAY: Failed to open platform-gpu-card, trying card1");
4556 CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded
4557 }
4558
4559 if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL))
4560 {
4561 TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0");
4562 CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3)
4563 }
4564 #endif
4565 if (-1 == CORE.Window.fd)
4566 {
4567 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card");
4568 return false;
4569 }
4570
4571 drmModeRes *res = drmModeGetResources(CORE.Window.fd);
4572 if (!res)
4573 {
4574 TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources");
4575 return false;
4576 }
4577
4578 TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors);
4579 for (size_t i = 0; i < res->count_connectors; i++)
4580 {
4581 TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i);
4582 drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]);
4583 TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes);
4584 if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id))
4585 {
4586 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected");
4587 CORE.Window.connector = con;
4588 break;
4589 }
4590 else
4591 {
4592 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)");
4593 drmModeFreeConnector(con);
4594 }
4595 }
4596
4597 if (!CORE.Window.connector)
4598 {
4599 TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found");
4600 drmModeFreeResources(res);
4601 return false;
4602 }
4603
4604 drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id);
4605 if (!enc)
4606 {
4607 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder");
4608 drmModeFreeResources(res);
4609 return false;
4610 }
4611
4612 CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id);
4613 if (!CORE.Window.crtc)
4614 {
4615 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc");
4616 drmModeFreeEncoder(enc);
4617 drmModeFreeResources(res);
4618 return false;
4619 }
4620
4621 // If InitWindow should use the current mode find it in the connector's mode list
4622 if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0))
4623 {
4624 TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode...");
4625
4626 CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode);
4627
4628 if (CORE.Window.modeIndex < 0)
4629 {
4630 TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found");
4631 drmModeFreeEncoder(enc);
4632 drmModeFreeResources(res);
4633 return false;
4634 }
4635
4636 CORE.Window.screen.width = CORE.Window.display.width;
4637 CORE.Window.screen.height = CORE.Window.display.height;
4638 }
4639
4640 const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT;
4641 const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60;
4642
4643 // Try to find an exact matching mode
4644 CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
4645
4646 // If nothing found, try to find a nearly matching mode
4647 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
4648
4649 // If nothing found, try to find an exactly matching mode including interlaced
4650 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
4651
4652 // If nothing found, try to find a nearly matching mode including interlaced
4653 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
4654
4655 // If nothing found, there is no suitable mode
4656 if (CORE.Window.modeIndex < 0)
4657 {
4658 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode");
4659 drmModeFreeEncoder(enc);
4660 drmModeFreeResources(res);
4661 return false;
4662 }
4663
4664 CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay;
4665 CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay;
4666
4667 TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name,
4668 CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay,
4669 (CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p',
4670 CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh);
4671
4672 // Use the width and height of the surface for render
4673 CORE.Window.render.width = CORE.Window.screen.width;
4674 CORE.Window.render.height = CORE.Window.screen.height;
4675
4676 drmModeFreeEncoder(enc);
4677 enc = NULL;
4678
4679 drmModeFreeResources(res);
4680 res = NULL;
4681
4682 CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd);
4683 if (!CORE.Window.gbmDevice)
4684 {
4685 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device");
4686 return false;
4687 }
4688
4689 CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay,
4690 CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
4691 if (!CORE.Window.gbmSurface)
4692 {
4693 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface");
4694 return false;
4695 }
4696 #endif
4697
4698 EGLint samples = 0;
4699 EGLint sampleBuffer = 0;
4700 if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
4701 {
4702 samples = 4;
4703 sampleBuffer = 1;
4704 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
4705 }
4706
4707 const EGLint framebufferAttribs[] =
4708 {
4709 EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support
4710 #if defined(PLATFORM_DRM)
4711 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android!
4712 #endif
4713 EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5)
4714 EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6)
4715 EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5)
4716 #if defined(PLATFORM_DRM)
4717 EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer)
4718 #endif
4719 //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI)
4720 EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!)
4721 //EGL_STENCIL_SIZE, 8, // Stencil buffer size
4722 EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA
4723 EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs)
4724 EGL_NONE
4725 };
4726
4727 const EGLint contextAttribs[] =
4728 {
4729 EGL_CONTEXT_CLIENT_VERSION, 2,
4730 EGL_NONE
4731 };
4732
4733 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
4734 EGLint numConfigs = 0;
4735
4736 // Get an EGL device connection
4737 #if defined(PLATFORM_DRM)
4738 CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice);
4739 #else
4740 CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY);
4741 #endif
4742 if (CORE.Window.device == EGL_NO_DISPLAY)
4743 {
4744 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
4745 return false;
4746 }
4747
4748 // Initialize the EGL device connection
4749 if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE)
4750 {
4751 // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred.
4752 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
4753 return false;
4754 }
4755
4756 #if defined(PLATFORM_DRM)
4757 if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs))
4758 {
4759 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError());
4760 return false;
4761 }
4762
4763 TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs);
4764
4765 EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs));
4766 if (!configs)
4767 {
4768 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs");
4769 return false;
4770 }
4771
4772 EGLint matchingNumConfigs = 0;
4773 if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs))
4774 {
4775 TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError());
4776 free(configs);
4777 return false;
4778 }
4779
4780 TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs);
4781
4782 // find the EGL config that matches the previously setup GBM format
4783 int found = 0;
4784 for (EGLint i = 0; i < matchingNumConfigs; ++i)
4785 {
4786 EGLint id = 0;
4787 if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id))
4788 {
4789 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError());
4790 continue;
4791 }
4792
4793 if (GBM_FORMAT_ARGB8888 == id)
4794 {
4795 TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i);
4796 CORE.Window.config = configs[i];
4797 found = 1;
4798 break;
4799 }
4800 }
4801
4802 RL_FREE(configs);
4803
4804 if (!found)
4805 {
4806 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config");
4807 return false;
4808 }
4809 #else
4810 // Get an appropriate EGL framebuffer configuration
4811 eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs);
4812 #endif
4813
4814 // Set rendering API
4815 eglBindAPI(EGL_OPENGL_ES_API);
4816
4817 // Create an EGL rendering context
4818 CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs);
4819 if (CORE.Window.context == EGL_NO_CONTEXT)
4820 {
4821 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context");
4822 return false;
4823 }
4824 #endif
4825
4826 // Create an EGL window surface
4827 //---------------------------------------------------------------------------------
4828 #if defined(PLATFORM_ANDROID)
4829 EGLint displayFormat = 0;
4830
4831 // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry()
4832 // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID
4833 eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
4834
4835 // At this point we need to manage render size vs screen size
4836 // NOTE: This function use and modify global module variables:
4837 // -> CORE.Window.screen.width/CORE.Window.screen.height
4838 // -> CORE.Window.render.width/CORE.Window.render.height
4839 // -> CORE.Window.screenScale
4840 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
4841
4842 ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat);
4843 //ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size
4844
4845 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL);
4846 #endif // PLATFORM_ANDROID
4847
4848 #if defined(PLATFORM_RPI)
4849 graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height);
4850
4851 // Screen size security check
4852 if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width;
4853 if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height;
4854
4855 // At this point we need to manage render size vs screen size
4856 // NOTE: This function use and modify global module variables:
4857 // -> CORE.Window.screen.width/CORE.Window.screen.height
4858 // -> CORE.Window.render.width/CORE.Window.render.height
4859 // -> CORE.Window.screenScale
4860 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
4861
4862 dstRect.x = 0;
4863 dstRect.y = 0;
4864 dstRect.width = CORE.Window.display.width;
4865 dstRect.height = CORE.Window.display.height;
4866
4867 srcRect.x = 0;
4868 srcRect.y = 0;
4869 srcRect.width = CORE.Window.render.width << 16;
4870 srcRect.height = CORE.Window.render.height << 16;
4871
4872 // NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost)
4873 // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio
4874
4875 VC_DISPMANX_ALPHA_T alpha = { 0 };
4876 alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS;
4877 //alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT
4878 alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE
4879 alpha.mask = 0;
4880
4881 dispmanDisplay = vc_dispmanx_display_open(0); // LCD
4882 dispmanUpdate = vc_dispmanx_update_start(0);
4883
4884 dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/,
4885 &srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE);
4886
4887 CORE.Window.handle.element = dispmanElement;
4888 CORE.Window.handle.width = CORE.Window.render.width;
4889 CORE.Window.handle.height = CORE.Window.render.height;
4890 vc_dispmanx_update_submit_sync(dispmanUpdate);
4891
4892 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL);
4893
4894 const unsigned char *const renderer = glGetString(GL_RENDERER);
4895 if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer);
4896 else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name");
4897 //---------------------------------------------------------------------------------
4898 #endif // PLATFORM_RPI
4899
4900 #if defined(PLATFORM_DRM)
4901 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL);
4902 if (EGL_NO_SURFACE == CORE.Window.surface)
4903 {
4904 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError());
4905 return false;
4906 }
4907
4908 // At this point we need to manage render size vs screen size
4909 // NOTE: This function use and modify global module variables:
4910 // -> CORE.Window.screen.width/CORE.Window.screen.height
4911 // -> CORE.Window.render.width/CORE.Window.render.height
4912 // -> CORE.Window.screenScale
4913 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
4914 #endif // PLATFORM_DRM
4915
4916 // There must be at least one frame displayed before the buffers are swapped
4917 //eglSwapInterval(CORE.Window.device, 1);
4918
4919 if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE)
4920 {
4921 TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface");
4922 return false;
4923 }
4924 else
4925 {
4926 CORE.Window.render.width = CORE.Window.screen.width;
4927 CORE.Window.render.height = CORE.Window.screen.height;
4928 CORE.Window.currentFbo.width = CORE.Window.render.width;
4929 CORE.Window.currentFbo.height = CORE.Window.render.height;
4930
4931 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
4932 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
4933 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
4934 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
4935 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
4936 }
4937 #endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM
4938
4939 // Load OpenGL extensions
4940 // NOTE: GL procedures address loader is required to load extensions
4941 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
4942 rlLoadExtensions(glfwGetProcAddress);
4943 #else
4944 rlLoadExtensions(eglGetProcAddress);
4945 #endif
4946
4947 // Initialize OpenGL context (states and resources)
4948 // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl
4949 rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height);
4950
4951 // Setup default viewport
4952 // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height
4953 SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height);
4954
4955 #if defined(PLATFORM_ANDROID)
4956 CORE.Window.ready = true;
4957 #endif
4958
4959 if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow();
4960
4961 return true;
4962 }
4963
4964 // Set viewport for a provided width and height
4965 static void SetupViewport(int width, int height)
4966 {
4967 CORE.Window.render.width = width;
4968 CORE.Window.render.height = height;
4969
4970 // Set viewport width and height
4971 // NOTE: We consider render size (scaled) and offset in case black bars are required and
4972 // render area does not match full display area (this situation is only applicable on fullscreen mode)
4973 #if defined(__APPLE__)
4974 float xScale = 1.0f, yScale = 1.0f;
4975 glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale);
4976 rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width)*xScale, (CORE.Window.render.height)*yScale);
4977 #else
4978 rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height);
4979 #endif
4980
4981 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
4982 rlLoadIdentity(); // Reset current matrix (projection)
4983
4984 // Set orthographic projection to current framebuffer size
4985 // NOTE: Configured top-left corner as (0, 0)
4986 rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f);
4987
4988 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
4989 rlLoadIdentity(); // Reset current matrix (modelview)
4990 }
4991
4992 // Compute framebuffer size relative to screen size and display size
4993 // NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified
4994 static void SetupFramebuffer(int width, int height)
4995 {
4996 // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var)
4997 if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height))
4998 {
4999 TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height);
5000
5001 // Downscaling to fit display with border-bars
5002 float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width;
5003 float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height;
5004
5005 if (widthRatio <= heightRatio)
5006 {
5007 CORE.Window.render.width = CORE.Window.display.width;
5008 CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio);
5009 CORE.Window.renderOffset.x = 0;
5010 CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height);
5011 }
5012 else
5013 {
5014 CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio);
5015 CORE.Window.render.height = CORE.Window.display.height;
5016 CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width);
5017 CORE.Window.renderOffset.y = 0;
5018 }
5019
5020 // Screen scaling required
5021 float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width;
5022 CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f);
5023
5024 // NOTE: We render to full display resolution!
5025 // We just need to calculate above parameters for downscale matrix and offsets
5026 CORE.Window.render.width = CORE.Window.display.width;
5027 CORE.Window.render.height = CORE.Window.display.height;
5028
5029 TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height);
5030 }
5031 else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height))
5032 {
5033 // Required screen size is smaller than display size
5034 TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height);
5035
5036 if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0))
5037 {
5038 CORE.Window.screen.width = CORE.Window.display.width;
5039 CORE.Window.screen.height = CORE.Window.display.height;
5040 }
5041
5042 // Upscaling to fit display with border-bars
5043 float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height;
5044 float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
5045
5046 if (displayRatio <= screenRatio)
5047 {
5048 CORE.Window.render.width = CORE.Window.screen.width;
5049 CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio);
5050 CORE.Window.renderOffset.x = 0;
5051 CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height);
5052 }
5053 else
5054 {
5055 CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio);
5056 CORE.Window.render.height = CORE.Window.screen.height;
5057 CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width);
5058 CORE.Window.renderOffset.y = 0;
5059 }
5060 }
5061 else
5062 {
5063 CORE.Window.render.width = CORE.Window.screen.width;
5064 CORE.Window.render.height = CORE.Window.screen.height;
5065 CORE.Window.renderOffset.x = 0;
5066 CORE.Window.renderOffset.y = 0;
5067 }
5068 }
5069
5070 // Initialize hi-resolution timer
5071 static void InitTimer(void)
5072 {
5073 // Setting a higher resolution can improve the accuracy of time-out intervals in wait functions.
5074 // However, it can also reduce overall system performance, because the thread scheduler switches tasks more often.
5075 // High resolutions can also prevent the CPU power management system from entering power-saving modes.
5076 // Setting a higher resolution does not improve the accuracy of the high-resolution performance counter.
5077 #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP)
5078 timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms)
5079 #endif
5080
5081 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
5082 struct timespec now = { 0 };
5083
5084 if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success
5085 {
5086 CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec;
5087 }
5088 else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available");
5089 #endif
5090
5091 CORE.Time.previous = GetTime(); // Get time as double
5092 }
5093
5094 // Wait for some time (stop program execution)
5095 // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could
5096 // take longer than expected... for that reason we use the busy wait loop
5097 // Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected
5098 // Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32!
5099 void WaitTime(double seconds)
5100 {
5101 #if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP)
5102 double destinationTime = GetTime() + seconds;
5103 #endif
5104
5105 #if defined(SUPPORT_BUSY_WAIT_LOOP)
5106 while (GetTime() < destinationTime) { }
5107 #else
5108 #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP)
5109 double sleepSeconds = seconds - seconds*0.05; // NOTE: We reserve a percentage of the time for busy waiting
5110 #else
5111 double sleepSeconds = seconds;
5112 #endif
5113
5114 // System halt functions
5115 #if defined(_WIN32)
5116 Sleep((unsigned long)(sleepSeconds*1000.0));
5117 #endif
5118 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__)
5119 struct timespec req = { 0 };
5120 time_t sec = sleepSeconds;
5121 long nsec = (sleepSeconds - sec)*1000000000L;
5122 req.tv_sec = sec;
5123 req.tv_nsec = nsec;
5124
5125 // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated.
5126 while (nanosleep(&req, &req) == -1) continue;
5127 #endif
5128 #if defined(__APPLE__)
5129 usleep(sleepSeconds*1000000.0);
5130 #endif
5131
5132 #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP)
5133 while (GetTime() < destinationTime) { }
5134 #endif
5135 #endif
5136 }
5137
5138 // Swap back buffer with front buffer (screen drawing)
5139 void SwapScreenBuffer(void)
5140 {
5141 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
5142 glfwSwapBuffers(CORE.Window.handle);
5143 #endif
5144
5145 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
5146 eglSwapBuffers(CORE.Window.device, CORE.Window.surface);
5147
5148 #if defined(PLATFORM_DRM)
5149
5150 if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
5151
5152 struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface);
5153 if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer");
5154
5155 uint32_t fb = 0;
5156 int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb);
5157 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result);
5158
5159 result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]);
5160 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result);
5161
5162 if (CORE.Window.prevFB)
5163 {
5164 result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB);
5165 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result);
5166 }
5167
5168 CORE.Window.prevFB = fb;
5169
5170 if (CORE.Window.prevBO) gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO);
5171
5172 CORE.Window.prevBO = bo;
5173
5174 #endif // PLATFORM_DRM
5175 #endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM
5176 }
5177
5178 // Register all input events
5179 void PollInputEvents(void)
5180 {
5181 #if defined(SUPPORT_GESTURES_SYSTEM)
5182 // NOTE: Gestures update must be called every frame to reset gestures correctly
5183 // because ProcessGestureEvent() is just called on an event, not every frame
5184 UpdateGestures();
5185 #endif
5186
5187 // Reset keys/chars pressed registered
5188 CORE.Input.Keyboard.keyPressedQueueCount = 0;
5189 CORE.Input.Keyboard.charPressedQueueCount = 0;
5190 // Reset key repeats
5191 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
5192
5193 #if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM))
5194 // Reset last gamepad button/axis registered state
5195 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
5196 CORE.Input.Gamepad.axisCount = 0;
5197 #endif
5198
5199 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
5200 // Register previous keys states
5201 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
5202 {
5203 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
5204 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
5205 }
5206
5207 PollKeyboardEvents();
5208
5209 // Register previous mouse states
5210 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
5211 CORE.Input.Mouse.currentWheelMove = CORE.Input.Mouse.eventWheelMove;
5212 CORE.Input.Mouse.eventWheelMove = (Vector2){ 0.0f, 0.0f };
5213 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++)
5214 {
5215 CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
5216 CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i];
5217 }
5218
5219 // Register gamepads buttons events
5220 for (int i = 0; i < MAX_GAMEPADS; i++)
5221 {
5222 if (CORE.Input.Gamepad.ready[i])
5223 {
5224 // Register previous gamepad states
5225 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
5226 }
5227 }
5228 #endif
5229
5230 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
5231 // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback)
5232
5233 // Register previous keys states
5234 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
5235 {
5236 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
5237 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
5238 }
5239
5240 // Register previous mouse states
5241 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
5242
5243 // Register previous mouse wheel state
5244 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
5245 CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
5246
5247 // Register previous mouse position
5248 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
5249 #endif
5250
5251 // Register previous touch states
5252 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
5253
5254 // Reset touch positions
5255 // TODO: It resets on PLATFORM_WEB the mouse position and not filled again until a move-event,
5256 // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed!
5257 //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 };
5258
5259 #if defined(PLATFORM_DESKTOP)
5260 // Check if gamepads are ready
5261 // NOTE: We do it here in case of disconnection
5262 for (int i = 0; i < MAX_GAMEPADS; i++)
5263 {
5264 if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true;
5265 else CORE.Input.Gamepad.ready[i] = false;
5266 }
5267
5268 // Register gamepads buttons events
5269 for (int i = 0; i < MAX_GAMEPADS; i++)
5270 {
5271 if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available
5272 {
5273 // Register previous gamepad states
5274 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
5275
5276 // Get current gamepad state
5277 // NOTE: There is no callback available, so we get it manually
5278 GLFWgamepadstate state = { 0 };
5279 glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller
5280
5281 const unsigned char *buttons = state.buttons;
5282
5283 for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++)
5284 {
5285 int button = -1; // GamepadButton enum values assigned
5286
5287 switch (k)
5288 {
5289 case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break;
5290 case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break;
5291 case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break;
5292 case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break;
5293
5294 case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break;
5295 case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break;
5296
5297 case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break;
5298 case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break;
5299 case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break;
5300
5301 case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break;
5302 case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break;
5303 case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break;
5304 case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break;
5305
5306 case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break;
5307 case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break;
5308 default: break;
5309 }
5310
5311 if (button != -1) // Check for valid button
5312 {
5313 if (buttons[k] == GLFW_PRESS)
5314 {
5315 CORE.Input.Gamepad.currentButtonState[i][button] = 1;
5316 CORE.Input.Gamepad.lastButtonPressed = button;
5317 }
5318 else CORE.Input.Gamepad.currentButtonState[i][button] = 0;
5319 }
5320 }
5321
5322 // Get current axis state
5323 const float *axes = state.axes;
5324
5325 for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++)
5326 {
5327 CORE.Input.Gamepad.axisState[i][k] = axes[k];
5328 }
5329
5330 // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis)
5331 CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f);
5332 CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1f);
5333
5334 CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1;
5335 }
5336 }
5337
5338 CORE.Window.resizedLastFrame = false;
5339
5340 if (CORE.Window.eventWaiting) glfwWaitEvents(); // Wait for in input events before continue (drawing is paused)
5341 else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks)
5342 #endif // PLATFORM_DESKTOP
5343
5344 #if defined(PLATFORM_WEB)
5345 CORE.Window.resizedLastFrame = false;
5346 #endif // PLATFORM_WEB
5347
5348 // Gamepad support using emscripten API
5349 // NOTE: GLFW3 joystick functionality not available in web
5350 #if defined(PLATFORM_WEB)
5351 // Get number of gamepads connected
5352 int numGamepads = 0;
5353 if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads();
5354
5355 for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++)
5356 {
5357 // Register previous gamepad button states
5358 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
5359
5360 EmscriptenGamepadEvent gamepadState;
5361
5362 int result = emscripten_get_gamepad_status(i, &gamepadState);
5363
5364 if (result == EMSCRIPTEN_RESULT_SUCCESS)
5365 {
5366 // Register buttons data for every connected gamepad
5367 for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++)
5368 {
5369 GamepadButton button = -1;
5370
5371 // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface
5372 switch (j)
5373 {
5374 case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break;
5375 case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break;
5376 case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break;
5377 case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break;
5378 case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break;
5379 case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break;
5380 case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break;
5381 case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break;
5382 case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break;
5383 case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break;
5384 case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break;
5385 case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break;
5386 case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break;
5387 case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break;
5388 case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break;
5389 case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break;
5390 default: break;
5391 }
5392
5393 if (button != -1) // Check for valid button
5394 {
5395 if (gamepadState.digitalButton[j] == 1)
5396 {
5397 CORE.Input.Gamepad.currentButtonState[i][button] = 1;
5398 CORE.Input.Gamepad.lastButtonPressed = button;
5399 }
5400 else CORE.Input.Gamepad.currentButtonState[i][button] = 0;
5401 }
5402
5403 //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]);
5404 }
5405
5406 // Register axis data for every connected gamepad
5407 for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++)
5408 {
5409 CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j];
5410 }
5411
5412 CORE.Input.Gamepad.axisCount = gamepadState.numAxes;
5413 }
5414 }
5415 #endif
5416
5417 #if defined(PLATFORM_ANDROID)
5418 // Register previous keys states
5419 // NOTE: Android supports up to 260 keys
5420 for (int i = 0; i < 260; i++)
5421 {
5422 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
5423 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
5424 }
5425
5426 // Android ALooper_pollAll() variables
5427 int pollResult = 0;
5428 int pollEvents = 0;
5429
5430 // Poll Events (registered events)
5431 // NOTE: Activity is paused if not enabled (CORE.Android.appEnabled)
5432 while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0)
5433 {
5434 // Process this event
5435 if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source);
5436
5437 // NOTE: Never close window, native activity is controlled by the system!
5438 if (CORE.Android.app->destroyRequested != 0)
5439 {
5440 //CORE.Window.shouldClose = true;
5441 //ANativeActivity_finish(CORE.Android.app->activity);
5442 }
5443 }
5444 #endif
5445
5446 #if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI)
5447 // NOTE: Keyboard reading could be done using input_event(s) or just read from stdin, both methods are used here.
5448 // stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console
5449
5450 if (!CORE.Input.Keyboard.evtMode) ProcessKeyboard();
5451
5452 // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread()
5453 // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
5454 #endif
5455 }
5456
5457 // Scan all files and directories in a base path
5458 // WARNING: files.paths[] must be previously allocated and
5459 // contain enough space to store all required paths
5460 static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter)
5461 {
5462 static char path[MAX_FILEPATH_LENGTH] = { 0 };
5463 memset(path, 0, MAX_FILEPATH_LENGTH);
5464
5465 struct dirent *dp = NULL;
5466 DIR *dir = opendir(basePath);
5467
5468 if (dir != NULL)
5469 {
5470 while ((dp = readdir(dir)) != NULL)
5471 {
5472 if ((strcmp(dp->d_name, ".") != 0) &&
5473 (strcmp(dp->d_name, "..") != 0))
5474 {
5475 sprintf(path, "%s/%s", basePath, dp->d_name);
5476
5477 if (filter != NULL)
5478 {
5479 if (IsFileExtension(path, filter))
5480 {
5481 strcpy(files->paths[files->count], path);
5482 files->count++;
5483 }
5484 }
5485 else
5486 {
5487 strcpy(files->paths[files->count], path);
5488 files->count++;
5489 }
5490 }
5491 }
5492
5493 closedir(dir);
5494 }
5495 else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath);
5496 }
5497
5498 // Scan all files and directories recursively from a base path
5499 static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter)
5500 {
5501 char path[MAX_FILEPATH_LENGTH] = { 0 };
5502 memset(path, 0, MAX_FILEPATH_LENGTH);
5503
5504 struct dirent *dp = NULL;
5505 DIR *dir = opendir(basePath);
5506
5507 if (dir != NULL)
5508 {
5509 while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity))
5510 {
5511 if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0))
5512 {
5513 // Construct new path from our base path
5514 sprintf(path, "%s/%s", basePath, dp->d_name);
5515
5516 if (IsPathFile(path))
5517 {
5518 if (filter != NULL)
5519 {
5520 if (IsFileExtension(path, filter))
5521 {
5522 strcpy(files->paths[files->count], path);
5523 files->count++;
5524 }
5525 }
5526 else
5527 {
5528 strcpy(files->paths[files->count], path);
5529 files->count++;
5530 }
5531
5532 if (files->count >= files->capacity)
5533 {
5534 TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity);
5535 break;
5536 }
5537 }
5538 else ScanDirectoryFilesRecursively(path, files, filter);
5539 }
5540 }
5541
5542 closedir(dir);
5543 }
5544 else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath);
5545 }
5546
5547 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
5548 // GLFW3 Error Callback, runs on GLFW3 error
5549 static void ErrorCallback(int error, const char *description)
5550 {
5551 TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description);
5552 }
5553
5554 // GLFW3 WindowSize Callback, runs when window is resizedLastFrame
5555 // NOTE: Window resizing not allowed by default
5556 static void WindowSizeCallback(GLFWwindow *window, int width, int height)
5557 {
5558 // Reset viewport and projection matrix for new size
5559 SetupViewport(width, height);
5560
5561 CORE.Window.currentFbo.width = width;
5562 CORE.Window.currentFbo.height = height;
5563 CORE.Window.resizedLastFrame = true;
5564
5565 if (IsWindowFullscreen()) return;
5566
5567 // Set current screen size
5568 #if defined(__APPLE__)
5569 CORE.Window.screen.width = width;
5570 CORE.Window.screen.height = height;
5571 #else
5572 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
5573 {
5574 Vector2 windowScaleDPI = GetWindowScaleDPI();
5575
5576 CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x);
5577 CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y);
5578 }
5579 else
5580 {
5581 CORE.Window.screen.width = width;
5582 CORE.Window.screen.height = height;
5583 }
5584 #endif
5585
5586 // NOTE: Postprocessing texture is not scaled to new size
5587 }
5588
5589 // GLFW3 WindowIconify Callback, runs when window is minimized/restored
5590 static void WindowIconifyCallback(GLFWwindow *window, int iconified)
5591 {
5592 if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified
5593 else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored
5594 }
5595
5596 #if !defined(PLATFORM_WEB)
5597 // GLFW3 WindowMaximize Callback, runs when window is maximized/restored
5598 static void WindowMaximizeCallback(GLFWwindow *window, int maximized)
5599 {
5600 if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized
5601 else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored
5602 }
5603 #endif
5604
5605 // GLFW3 WindowFocus Callback, runs when window get/lose focus
5606 static void WindowFocusCallback(GLFWwindow *window, int focused)
5607 {
5608 if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused
5609 else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus
5610 }
5611
5612 // GLFW3 Keyboard Callback, runs on key pressed
5613 static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
5614 {
5615 if (key < 0) return; // Security check, macOS fn key generates -1
5616
5617 // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1
5618 // to work properly with our implementation (IsKeyDown/IsKeyUp checks)
5619 if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0;
5620 else if(action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1;
5621 else if(action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1;
5622
5623 #if !defined(PLATFORM_WEB)
5624 // WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys
5625 if (((key == KEY_CAPS_LOCK) && ((mods & GLFW_MOD_CAPS_LOCK) > 0)) ||
5626 ((key == KEY_NUM_LOCK) && ((mods & GLFW_MOD_NUM_LOCK) > 0))) CORE.Input.Keyboard.currentKeyState[key] = 1;
5627 #endif
5628
5629 // Check if there is space available in the key queue
5630 if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS))
5631 {
5632 // Add character to the queue
5633 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
5634 CORE.Input.Keyboard.keyPressedQueueCount++;
5635 }
5636
5637 // Check the exit key to set close window
5638 if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE);
5639
5640 #if defined(SUPPORT_SCREEN_CAPTURE)
5641 if ((key == GLFW_KEY_F12) && (action == GLFW_PRESS))
5642 {
5643 #if defined(SUPPORT_GIF_RECORDING)
5644 if (mods & GLFW_MOD_CONTROL)
5645 {
5646 if (gifRecording)
5647 {
5648 gifRecording = false;
5649
5650 MsfGifResult result = msf_gif_end(&gifState);
5651
5652 SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.Storage.basePath, screenshotCounter), result.data, (unsigned int)result.dataSize);
5653 msf_gif_free(result);
5654
5655 #if defined(PLATFORM_WEB)
5656 // Download file from MEMFS (emscripten memory filesystem)
5657 // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html
5658 emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1)));
5659 #endif
5660
5661 TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording");
5662 }
5663 else
5664 {
5665 gifRecording = true;
5666 gifFrameCounter = 0;
5667
5668 Vector2 scale = GetWindowScaleDPI();
5669 msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y));
5670 screenshotCounter++;
5671
5672 TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter));
5673 }
5674 }
5675 else
5676 #endif // SUPPORT_GIF_RECORDING
5677 {
5678 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
5679 screenshotCounter++;
5680 }
5681 }
5682 #endif // SUPPORT_SCREEN_CAPTURE
5683
5684 #if defined(SUPPORT_EVENTS_AUTOMATION)
5685 if ((key == GLFW_KEY_F11) && (action == GLFW_PRESS))
5686 {
5687 eventsRecording = !eventsRecording;
5688
5689 // On finish recording, we export events into a file
5690 if (!eventsRecording) ExportAutomationEvents("eventsrec.rep");
5691 }
5692 else if ((key == GLFW_KEY_F9) && (action == GLFW_PRESS))
5693 {
5694 LoadAutomationEvents("eventsrec.rep");
5695 eventsPlaying = true;
5696
5697 TRACELOG(LOG_WARNING, "eventsPlaying enabled!");
5698 }
5699 #endif
5700 }
5701
5702 // GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value)
5703 static void CharCallback(GLFWwindow *window, unsigned int key)
5704 {
5705 //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key);
5706
5707 // NOTE: Registers any key down considering OS keyboard layout but
5708 // does not detect action events, those should be managed by user...
5709 // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907
5710 // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char
5711
5712 // Check if there is space available in the queue
5713 if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
5714 {
5715 // Add character to the queue
5716 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key;
5717 CORE.Input.Keyboard.charPressedQueueCount++;
5718 }
5719 }
5720
5721 // GLFW3 Mouse Button Callback, runs on mouse button pressed
5722 static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods)
5723 {
5724 // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now,
5725 // but future releases may add more actions (i.e. GLFW_REPEAT)
5726 CORE.Input.Mouse.currentButtonState[button] = action;
5727
5728 #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP
5729 // Process mouse events as touches to be able to use mouse-gestures
5730 GestureEvent gestureEvent = { 0 };
5731
5732 // Register touch actions
5733 if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
5734 else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP;
5735
5736 // NOTE: TOUCH_ACTION_MOVE event is registered in MouseCursorPosCallback()
5737
5738 // Assign a pointer ID
5739 gestureEvent.pointId[0] = 0;
5740
5741 // Register touch points count
5742 gestureEvent.pointCount = 1;
5743
5744 // Register touch points position, only one point registered
5745 gestureEvent.position[0] = GetMousePosition();
5746
5747 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height
5748 gestureEvent.position[0].x /= (float)GetScreenWidth();
5749 gestureEvent.position[0].y /= (float)GetScreenHeight();
5750
5751 // Gesture data is sent to gestures-system for processing
5752 #if defined(PLATFORM_WEB)
5753 // Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself
5754 if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent);
5755 #else
5756 ProcessGestureEvent(gestureEvent);
5757 #endif
5758
5759 #endif
5760 }
5761
5762 // GLFW3 Cursor Position Callback, runs on mouse move
5763 static void MouseCursorPosCallback(GLFWwindow *window, double x, double y)
5764 {
5765 CORE.Input.Mouse.currentPosition.x = (float)x;
5766 CORE.Input.Mouse.currentPosition.y = (float)y;
5767 CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
5768
5769 #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP
5770 // Process mouse events as touches to be able to use mouse-gestures
5771 GestureEvent gestureEvent = { 0 };
5772
5773 gestureEvent.touchAction = TOUCH_ACTION_MOVE;
5774
5775 // Assign a pointer ID
5776 gestureEvent.pointId[0] = 0;
5777
5778 // Register touch points count
5779 gestureEvent.pointCount = 1;
5780
5781 // Register touch points position, only one point registered
5782 gestureEvent.position[0] = CORE.Input.Touch.position[0];
5783
5784 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height
5785 gestureEvent.position[0].x /= (float)GetScreenWidth();
5786 gestureEvent.position[0].y /= (float)GetScreenHeight();
5787
5788 // Gesture data is sent to gestures-system for processing
5789 ProcessGestureEvent(gestureEvent);
5790 #endif
5791 }
5792
5793 // GLFW3 Scrolling Callback, runs on mouse wheel
5794 static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset)
5795 {
5796 CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset };
5797 }
5798
5799 // GLFW3 CursorEnter Callback, when cursor enters the window
5800 static void CursorEnterCallback(GLFWwindow *window, int enter)
5801 {
5802 if (enter == true) CORE.Input.Mouse.cursorOnScreen = true;
5803 else CORE.Input.Mouse.cursorOnScreen = false;
5804 }
5805
5806 // GLFW3 Window Drop Callback, runs when drop files into window
5807 static void WindowDropCallback(GLFWwindow *window, int count, const char **paths)
5808 {
5809 if (count > 0)
5810 {
5811 // In case previous dropped filepaths have not been freed, we free them
5812 if (CORE.Window.dropFileCount > 0)
5813 {
5814 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]);
5815
5816 RL_FREE(CORE.Window.dropFilepaths);
5817
5818 CORE.Window.dropFileCount = 0;
5819 CORE.Window.dropFilepaths = NULL;
5820 }
5821
5822 // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy
5823 CORE.Window.dropFileCount = count;
5824 CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *));
5825
5826 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++)
5827 {
5828 CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char));
5829 strcpy(CORE.Window.dropFilepaths[i], paths[i]);
5830 }
5831 }
5832 }
5833 #endif
5834
5835 #if defined(PLATFORM_ANDROID)
5836 // ANDROID: Process activity lifecycle commands
5837 static void AndroidCommandCallback(struct android_app *app, int32_t cmd)
5838 {
5839 switch (cmd)
5840 {
5841 case APP_CMD_START:
5842 {
5843 //rendering = true;
5844 } break;
5845 case APP_CMD_RESUME: break;
5846 case APP_CMD_INIT_WINDOW:
5847 {
5848 if (app->window != NULL)
5849 {
5850 if (CORE.Android.contextRebindRequired)
5851 {
5852 // Reset screen scaling to full display size
5853 EGLint displayFormat = 0;
5854 eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
5855
5856 // Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the
5857 // context rebinding if the screen is scaled unless offsets are added. There's probably a more
5858 // appropriate way to fix this
5859 ANativeWindow_setBuffersGeometry(app->window,
5860 CORE.Window.render.width + CORE.Window.renderOffset.x,
5861 CORE.Window.render.height + CORE.Window.renderOffset.y,
5862 displayFormat);
5863
5864 // Recreate display surface and re-attach OpenGL context
5865 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL);
5866 eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context);
5867
5868 CORE.Android.contextRebindRequired = false;
5869 }
5870 else
5871 {
5872 CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window);
5873 CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window);
5874
5875 // Initialize graphics device (display device and OpenGL context)
5876 InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height);
5877
5878 // Initialize hi-res timer
5879 InitTimer();
5880
5881 // Initialize random seed
5882 srand((unsigned int)time(NULL));
5883
5884 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT)
5885 // Load default font
5886 // WARNING: External function: Module required: rtext
5887 LoadFontDefault();
5888 Rectangle rec = GetFontDefault().recs[95];
5889 // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering
5890 #if defined(SUPPORT_MODULE_RSHAPES)
5891 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes
5892 #endif
5893 #endif
5894
5895 // TODO: GPU assets reload in case of lost focus (lost context)
5896 // NOTE: This problem has been solved just unbinding and rebinding context from display
5897 /*
5898 if (assetsReloadRequired)
5899 {
5900 for (int i = 0; i < assetCount; i++)
5901 {
5902 // TODO: Unload old asset if required
5903
5904 // Load texture again to pointed texture
5905 (*textureAsset + i) = LoadTexture(assetPath[i]);
5906 }
5907 }
5908 */
5909 }
5910 }
5911 } break;
5912 case APP_CMD_GAINED_FOCUS:
5913 {
5914 CORE.Android.appEnabled = true;
5915 //ResumeMusicStream();
5916 } break;
5917 case APP_CMD_PAUSE: break;
5918 case APP_CMD_LOST_FOCUS:
5919 {
5920 CORE.Android.appEnabled = false;
5921 //PauseMusicStream();
5922 } break;
5923 case APP_CMD_TERM_WINDOW:
5924 {
5925 // Detach OpenGL context and destroy display surface
5926 // NOTE 1: This case is used when the user exits the app without closing it. We detach the context to ensure everything is recoverable upon resuming.
5927 // NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...)
5928 // NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :(
5929 if (CORE.Window.device != EGL_NO_DISPLAY)
5930 {
5931 eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
5932
5933 if (CORE.Window.surface != EGL_NO_SURFACE)
5934 {
5935 eglDestroySurface(CORE.Window.device, CORE.Window.surface);
5936 CORE.Window.surface = EGL_NO_SURFACE;
5937 }
5938
5939 CORE.Android.contextRebindRequired = true;
5940 }
5941 // If 'CORE.Window.device' is already set to 'EGL_NO_DISPLAY'
5942 // this means that the user has already called 'CloseWindow()'
5943
5944 } break;
5945 case APP_CMD_SAVE_STATE: break;
5946 case APP_CMD_STOP: break;
5947 case APP_CMD_DESTROY: break;
5948 case APP_CMD_CONFIG_CHANGED:
5949 {
5950 //AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager);
5951 //print_cur_config(CORE.Android.app);
5952
5953 // Check screen orientation here!
5954 } break;
5955 default: break;
5956 }
5957 }
5958
5959 static GamepadButton AndroidTranslateGamepadButton(int button)
5960 {
5961 switch (button)
5962 {
5963 case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN;
5964 case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT;
5965 case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT;
5966 case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP;
5967 case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1;
5968 case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1;
5969 case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2;
5970 case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2;
5971 case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB;
5972 case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB;
5973 case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT;
5974 case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT;
5975 case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE;
5976 // On some (most?) gamepads dpad events are reported as axis motion instead
5977 case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN;
5978 case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT;
5979 case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT;
5980 case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP;
5981 default: return GAMEPAD_BUTTON_UNKNOWN;
5982 }
5983 }
5984
5985 // ANDROID: Get input events
5986 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event)
5987 {
5988 // If additional inputs are required check:
5989 // https://developer.android.com/ndk/reference/group/input
5990 // https://developer.android.com/training/game-controllers/controller-input
5991
5992 int type = AInputEvent_getType(event);
5993 int source = AInputEvent_getSource(event);
5994
5995 if (type == AINPUT_EVENT_TYPE_MOTION)
5996 {
5997 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) ||
5998 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD))
5999 {
6000 // For now we'll assume a single gamepad which we "detect" on its input event
6001 CORE.Input.Gamepad.ready[0] = true;
6002
6003 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue(
6004 event, AMOTION_EVENT_AXIS_X, 0);
6005 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue(
6006 event, AMOTION_EVENT_AXIS_Y, 0);
6007 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue(
6008 event, AMOTION_EVENT_AXIS_Z, 0);
6009 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue(
6010 event, AMOTION_EVENT_AXIS_RZ, 0);
6011 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue(
6012 event, AMOTION_EVENT_AXIS_BRAKE, 0) * 2.0f - 1.0f;
6013 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue(
6014 event, AMOTION_EVENT_AXIS_GAS, 0) * 2.0f - 1.0f;
6015
6016 // dpad is reported as an axis on android
6017 float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0);
6018 float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0);
6019
6020 if (dpadX == 1.0f)
6021 {
6022 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1;
6023 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0;
6024 }
6025 else if (dpadX == -1.0f)
6026 {
6027 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0;
6028 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1;
6029 }
6030 else
6031 {
6032 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0;
6033 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0;
6034 }
6035
6036 if (dpadY == 1.0f)
6037 {
6038 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1;
6039 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0;
6040 }
6041 else if (dpadY == -1.0f)
6042 {
6043 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0;
6044 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1;
6045 }
6046 else
6047 {
6048 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0;
6049 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0;
6050 }
6051
6052 return 1; // Handled gamepad axis motion
6053 }
6054 }
6055 else if (type == AINPUT_EVENT_TYPE_KEY)
6056 {
6057 int32_t keycode = AKeyEvent_getKeyCode(event);
6058 //int32_t AKeyEvent_getMetaState(event);
6059
6060 // Handle gamepad button presses and releases
6061 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) ||
6062 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD))
6063 {
6064 // For now we'll assume a single gamepad which we "detect" on its input event
6065 CORE.Input.Gamepad.ready[0] = true;
6066
6067 GamepadButton button = AndroidTranslateGamepadButton(keycode);
6068
6069 if (button == GAMEPAD_BUTTON_UNKNOWN) return 1;
6070
6071 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
6072 {
6073 CORE.Input.Gamepad.currentButtonState[0][button] = 1;
6074 }
6075 else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up
6076
6077 return 1; // Handled gamepad button
6078 }
6079
6080 // Save current button and its state
6081 // NOTE: Android key action is 0 for down and 1 for up
6082 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
6083 {
6084 CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down
6085
6086 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
6087 CORE.Input.Keyboard.keyPressedQueueCount++;
6088 }
6089 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[keycode] = 1;
6090 else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up
6091
6092 if (keycode == AKEYCODE_POWER)
6093 {
6094 // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS
6095 // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS
6096 // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected.
6097 // NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" >
6098 // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour
6099 return 0;
6100 }
6101 else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU))
6102 {
6103 // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS!
6104 return 1;
6105 }
6106 else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN))
6107 {
6108 // Set default OS behaviour
6109 return 0;
6110 }
6111
6112 return 0;
6113 }
6114
6115 // Register touch points count
6116 CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event);
6117
6118 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++)
6119 {
6120 // Register touch points id
6121 CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i);
6122
6123 // Register touch points position
6124 CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) };
6125
6126 // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height
6127 float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x) / (float)CORE.Window.display.width;
6128 float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y) / (float)CORE.Window.display.height;
6129 CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x * widthRatio - (float)CORE.Window.renderOffset.x / 2;
6130 CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y * heightRatio - (float)CORE.Window.renderOffset.y / 2;
6131 }
6132
6133 int32_t action = AMotionEvent_getAction(event);
6134 unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
6135
6136 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_ANDROID
6137 GestureEvent gestureEvent = { 0 };
6138
6139 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
6140
6141 // Register touch actions
6142 if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
6143 else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP;
6144 else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE;
6145 else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL;
6146
6147 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++)
6148 {
6149 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i];
6150 gestureEvent.position[i] = CORE.Input.Touch.position[i];
6151 }
6152
6153 // Gesture data is sent to gestures system for processing
6154 ProcessGestureEvent(gestureEvent);
6155 #endif
6156
6157 int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
6158
6159 if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP)
6160 {
6161 // One of the touchpoints is released, remove it from touch point arrays
6162 for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount - 1) && (i < MAX_TOUCH_POINTS); i++)
6163 {
6164 CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1];
6165 CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1];
6166 }
6167
6168 CORE.Input.Touch.pointCount--;
6169 }
6170
6171 // When all touchpoints are tapped and released really quickly, this event is generated
6172 if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0;
6173
6174 if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1;
6175 else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0;
6176
6177 return 0;
6178 }
6179 #endif
6180
6181 #if defined(PLATFORM_WEB)
6182 // Register fullscreen change events
6183 static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData)
6184 {
6185 // TODO: Implement EmscriptenFullscreenChangeCallback()?
6186
6187 return 1; // The event was consumed by the callback handler
6188 }
6189
6190 // Register window resize event
6191 static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData)
6192 {
6193 // TODO: Implement EmscriptenWindowResizedCallback()?
6194
6195 return 1; // The event was consumed by the callback handler
6196 }
6197
6198 EM_JS(int, GetCanvasWidth, (), { return canvas.clientWidth; });
6199 EM_JS(int, GetCanvasHeight, (), { return canvas.clientHeight; });
6200
6201 // Register DOM element resize event
6202 static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData)
6203 {
6204 // Don't resize non-resizeable windows
6205 if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1;
6206
6207 // This event is called whenever the window changes sizes,
6208 // so the size of the canvas object is explicitly retrieved below
6209 int width = GetCanvasWidth();
6210 int height = GetCanvasHeight();
6211 emscripten_set_canvas_element_size("#canvas",width,height);
6212
6213 SetupViewport(width, height); // Reset viewport and projection matrix for new size
6214
6215 CORE.Window.currentFbo.width = width;
6216 CORE.Window.currentFbo.height = height;
6217 CORE.Window.resizedLastFrame = true;
6218
6219 if (IsWindowFullscreen()) return 1;
6220
6221 // Set current screen size
6222 CORE.Window.screen.width = width;
6223 CORE.Window.screen.height = height;
6224
6225 // NOTE: Postprocessing texture is not scaled to new size
6226
6227 return 0;
6228 }
6229
6230 // Register mouse input events
6231 static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
6232 {
6233 // This is only for registering mouse click events with emscripten and doesn't need to do anything
6234
6235 return 1; // The event was consumed by the callback handler
6236 }
6237
6238 // Register connected/disconnected gamepads events
6239 static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
6240 {
6241 /*
6242 TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"",
6243 eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state",
6244 gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping);
6245
6246 for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]);
6247 for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]);
6248 */
6249
6250 if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS))
6251 {
6252 CORE.Input.Gamepad.ready[gamepadEvent->index] = true;
6253 sprintf(CORE.Input.Gamepad.name[gamepadEvent->index],"%s",gamepadEvent->id);
6254 }
6255 else CORE.Input.Gamepad.ready[gamepadEvent->index] = false;
6256
6257 return 1; // The event was consumed by the callback handler
6258 }
6259
6260 // Register touch input events
6261 static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
6262 {
6263 // Register touch points count
6264 CORE.Input.Touch.pointCount = touchEvent->numTouches;
6265
6266 double canvasWidth = 0.0;
6267 double canvasHeight = 0.0;
6268 // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but
6269 // we are looking for actual CSS size: canvas.style.width and canvas.style.height
6270 //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight);
6271 emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight);
6272
6273 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++)
6274 {
6275 // Register touch points id
6276 CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier;
6277
6278 // Register touch points position
6279 CORE.Input.Touch.position[i] = (Vector2){ touchEvent->touches[i].targetX, touchEvent->touches[i].targetY };
6280
6281 // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height
6282 CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth);
6283 CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight);
6284
6285 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1;
6286 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0;
6287 }
6288
6289 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_WEB
6290 GestureEvent gestureEvent = { 0 };
6291
6292 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
6293
6294 // Register touch actions
6295 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
6296 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP;
6297 else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE;
6298 else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL;
6299
6300 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++)
6301 {
6302 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i];
6303 gestureEvent.position[i] = CORE.Input.Touch.position[i];
6304
6305 // Normalize gestureEvent.position[i]
6306 gestureEvent.position[i].x /= (float)GetScreenWidth();
6307 gestureEvent.position[i].y /= (float)GetScreenHeight();
6308 }
6309
6310 // Gesture data is sent to gestures system for processing
6311 ProcessGestureEvent(gestureEvent);
6312
6313 // Reset the pointCount for web, if it was the last Touch End event
6314 if (eventType == EMSCRIPTEN_EVENT_TOUCHEND && CORE.Input.Touch.pointCount == 1) CORE.Input.Touch.pointCount = 0;
6315 #endif
6316
6317 return 1; // The event was consumed by the callback handler
6318 }
6319 #endif
6320
6321 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
6322 // Initialize Keyboard system (using standard input)
6323 static void InitKeyboard(void)
6324 {
6325 // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor,
6326 // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE
6327
6328 // Save terminal keyboard settings
6329 tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings);
6330
6331 // Reconfigure terminal with new settings
6332 struct termios keyboardNewSettings = { 0 };
6333 keyboardNewSettings = CORE.Input.Keyboard.defaultSettings;
6334
6335 // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing
6336 // NOTE: ISIG controls if ^C and ^Z generate break signals or not
6337 keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG);
6338 //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF);
6339 keyboardNewSettings.c_cc[VMIN] = 1;
6340 keyboardNewSettings.c_cc[VTIME] = 0;
6341
6342 // Set new keyboard settings (change occurs immediately)
6343 tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings);
6344
6345 // Save old keyboard mode to restore it at the end
6346 CORE.Input.Keyboard.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags
6347 fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified
6348
6349 // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno)
6350 int result = ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode);
6351
6352 // In case of failure, it could mean a remote keyboard is used (SSH)
6353 if (result < 0) TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode, an SSH keyboard is probably used");
6354 else
6355 {
6356 // Reconfigure keyboard mode to get:
6357 // - scancodes (K_RAW)
6358 // - keycodes (K_MEDIUMRAW)
6359 // - ASCII chars (K_XLATE)
6360 // - UNICODE chars (K_UNICODE)
6361 ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars
6362 }
6363
6364 // Register keyboard restore when program finishes
6365 atexit(RestoreKeyboard);
6366 }
6367
6368 // Restore default keyboard input
6369 static void RestoreKeyboard(void)
6370 {
6371 // Reset to default keyboard settings
6372 tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings);
6373
6374 // Reconfigure keyboard to default mode
6375 fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags);
6376 ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode);
6377 }
6378
6379 #if defined(SUPPORT_SSH_KEYBOARD_RPI)
6380 // Process keyboard inputs
6381 static void ProcessKeyboard(void)
6382 {
6383 #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read
6384
6385 // Keyboard input polling (fill keys[256] array with status)
6386 int bufferByteCount = 0; // Bytes available on the buffer
6387 char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time
6388
6389 // Read availables keycodes from stdin
6390 bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call
6391
6392 // Reset pressed keys array (it will be filled below)
6393 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
6394 {
6395 CORE.Input.Keyboard.currentKeyState[i] = 0;
6396 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
6397 }
6398
6399 // Fill all read bytes (looking for keys)
6400 for (int i = 0; i < bufferByteCount; i++)
6401 {
6402 // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code!
6403 // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42
6404 if (keysBuffer[i] == 0x1b)
6405 {
6406 // Check if ESCAPE key has been pressed to stop program
6407 if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1;
6408 else
6409 {
6410 if (keysBuffer[i + 1] == 0x5b) // Special function key
6411 {
6412 if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32))
6413 {
6414 // Process special function keys (F1 - F12)
6415 switch (keysBuffer[i + 3])
6416 {
6417 case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1
6418 case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2
6419 case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3
6420 case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4
6421 case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5
6422 case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6
6423 case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7
6424 case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8
6425 case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9
6426 case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10
6427 case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11
6428 case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12
6429 default: break;
6430 }
6431
6432 if (keysBuffer[i + 2] == 0x5b) i += 4;
6433 else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5;
6434 }
6435 else
6436 {
6437 switch (keysBuffer[i + 2])
6438 {
6439 case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP
6440 case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN
6441 case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT
6442 case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT
6443 default: break;
6444 }
6445
6446 i += 3; // Jump to next key
6447 }
6448
6449 // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT)
6450 }
6451 }
6452 }
6453 else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*)
6454 {
6455 CORE.Input.Keyboard.currentKeyState[257] = 1;
6456
6457 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue
6458 CORE.Input.Keyboard.keyPressedQueueCount++;
6459 }
6460 else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE
6461 {
6462 CORE.Input.Keyboard.currentKeyState[259] = 1;
6463
6464 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue
6465 CORE.Input.Keyboard.keyPressedQueueCount++;
6466 }
6467 else
6468 {
6469 // Translate lowercase a-z letters to A-Z
6470 if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122))
6471 {
6472 CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1;
6473 }
6474 else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1;
6475
6476 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue
6477 CORE.Input.Keyboard.keyPressedQueueCount++;
6478 }
6479 }
6480
6481 // Check exit key (same functionality as GLFW3 KeyCallback())
6482 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
6483
6484 #if defined(SUPPORT_SCREEN_CAPTURE)
6485 // Check screen capture key (raylib key: KEY_F12)
6486 if (CORE.Input.Keyboard.currentKeyState[301] == 1)
6487 {
6488 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
6489 screenshotCounter++;
6490 }
6491 #endif
6492 }
6493 #endif // SUPPORT_SSH_KEYBOARD_RPI
6494
6495 // Initialise user input from evdev(/dev/input/event<N>) this means mouse, keyboard or gamepad devices
6496 static void InitEvdevInput(void)
6497 {
6498 char path[MAX_FILEPATH_LENGTH] = { 0 };
6499 DIR *directory = NULL;
6500 struct dirent *entity = NULL;
6501
6502 // Initialise keyboard file descriptor
6503 CORE.Input.Keyboard.fd = -1;
6504
6505 // Reset variables
6506 for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
6507 {
6508 CORE.Input.Touch.position[i].x = -1;
6509 CORE.Input.Touch.position[i].y = -1;
6510 }
6511
6512 // Reset keyboard key state
6513 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
6514 {
6515 CORE.Input.Keyboard.currentKeyState[i] = 0;
6516 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
6517 }
6518
6519 // Open the linux directory of "/dev/input"
6520 directory = opendir(DEFAULT_EVDEV_PATH);
6521
6522 if (directory)
6523 {
6524 while ((entity = readdir(directory)) != NULL)
6525 {
6526 if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*"
6527 (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*"
6528 {
6529 sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name);
6530 ConfigureEvdevDevice(path); // Configure the device if appropriate
6531 }
6532 }
6533
6534 closedir(directory);
6535 }
6536 else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH);
6537 }
6538
6539 // Identifies a input device and configures it for use if appropriate
6540 static void ConfigureEvdevDevice(char *device)
6541 {
6542 #define BITS_PER_LONG (8*sizeof(long))
6543 #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1)
6544 #define OFF(x) ((x)%BITS_PER_LONG)
6545 #define BIT(x) (1UL<<OFF(x))
6546 #define LONG(x) ((x)/BITS_PER_LONG)
6547 #define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1)
6548
6549 struct input_absinfo absinfo = { 0 };
6550 unsigned long evBits[NBITS(EV_MAX)] = { 0 };
6551 unsigned long absBits[NBITS(ABS_MAX)] = { 0 };
6552 unsigned long relBits[NBITS(REL_MAX)] = { 0 };
6553 unsigned long keyBits[NBITS(KEY_MAX)] = { 0 };
6554 bool hasAbs = false;
6555 bool hasRel = false;
6556 bool hasAbsMulti = false;
6557 int freeWorkerId = -1;
6558 int fd = -1;
6559
6560 InputEventWorker *worker = NULL;
6561
6562 // Open the device and allocate worker
6563 //-------------------------------------------------------------------------------------------------------
6564 // Find a free spot in the workers array
6565 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
6566 {
6567 if (CORE.Input.eventWorker[i].threadId == 0)
6568 {
6569 freeWorkerId = i;
6570 break;
6571 }
6572 }
6573
6574 // Select the free worker from array
6575 if (freeWorkerId >= 0)
6576 {
6577 worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker
6578 memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker
6579 }
6580 else
6581 {
6582 TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device);
6583 return;
6584 }
6585
6586 // Open the device
6587 fd = open(device, O_RDONLY | O_NONBLOCK);
6588 if (fd < 0)
6589 {
6590 TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device);
6591 return;
6592 }
6593 worker->fd = fd;
6594
6595 // Grab number on the end of the devices name "event<N>"
6596 int devNum = 0;
6597 char *ptrDevName = strrchr(device, 't');
6598 worker->eventNum = -1;
6599
6600 if (ptrDevName != NULL)
6601 {
6602 if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum;
6603 }
6604 else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device!
6605
6606 // At this point we have a connection to the device, but we don't yet know what the device is.
6607 // It could be many things, even as simple as a power button...
6608 //-------------------------------------------------------------------------------------------------------
6609
6610 // Identify the device
6611 //-------------------------------------------------------------------------------------------------------
6612 ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties
6613
6614 // Check for absolute input devices
6615 if (TEST_BIT(evBits, EV_ABS))
6616 {
6617 ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits);
6618
6619 // Check for absolute movement support (usually touchscreens, but also joysticks)
6620 if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y))
6621 {
6622 hasAbs = true;
6623
6624 // Get the scaling values
6625 ioctl(fd, EVIOCGABS(ABS_X), &absinfo);
6626 worker->absRange.x = absinfo.minimum;
6627 worker->absRange.width = absinfo.maximum - absinfo.minimum;
6628 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo);
6629 worker->absRange.y = absinfo.minimum;
6630 worker->absRange.height = absinfo.maximum - absinfo.minimum;
6631 }
6632
6633 // Check for multiple absolute movement support (usually multitouch touchscreens)
6634 if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y))
6635 {
6636 hasAbsMulti = true;
6637
6638 // Get the scaling values
6639 ioctl(fd, EVIOCGABS(ABS_X), &absinfo);
6640 worker->absRange.x = absinfo.minimum;
6641 worker->absRange.width = absinfo.maximum - absinfo.minimum;
6642 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo);
6643 worker->absRange.y = absinfo.minimum;
6644 worker->absRange.height = absinfo.maximum - absinfo.minimum;
6645 }
6646 }
6647
6648 // Check for relative movement support (usually mouse)
6649 if (TEST_BIT(evBits, EV_REL))
6650 {
6651 ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits);
6652
6653 if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true;
6654 }
6655
6656 // Check for button support to determine the device type(usually on all input devices)
6657 if (TEST_BIT(evBits, EV_KEY))
6658 {
6659 ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits);
6660
6661 if (hasAbs || hasAbsMulti)
6662 {
6663 if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen
6664 if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet
6665 if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet
6666 if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet
6667 if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device
6668 }
6669
6670 if (hasRel)
6671 {
6672 if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse
6673 if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse
6674 }
6675
6676 if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad
6677 if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad
6678 if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad
6679 if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad
6680 if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad
6681
6682 if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard
6683 }
6684 //-------------------------------------------------------------------------------------------------------
6685
6686 // Decide what to do with the device
6687 //-------------------------------------------------------------------------------------------------------
6688 if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1))
6689 {
6690 // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a
6691 // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate
6692 // threads so that they don't drop events when the frame rate is slow.
6693 TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device);
6694 CORE.Input.Keyboard.fd = worker->fd;
6695 }
6696 else if (worker->isTouch || worker->isMouse)
6697 {
6698 // Looks like an interesting device
6699 TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device,
6700 worker->isMouse? "mouse " : "",
6701 worker->isMultitouch? "multitouch " : "",
6702 worker->isTouch? "touchscreen " : "",
6703 worker->isGamepad? "gamepad " : "");
6704
6705 // Create a thread for this device
6706 int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker);
6707 if (error != 0)
6708 {
6709 TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error);
6710 worker->threadId = 0;
6711 close(fd);
6712 }
6713
6714 #if defined(USE_LAST_TOUCH_DEVICE)
6715 // Find touchscreen with the highest index
6716 int maxTouchNumber = -1;
6717
6718 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
6719 {
6720 if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum;
6721 }
6722
6723 // Find touchscreens with lower indexes
6724 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
6725 {
6726 if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber))
6727 {
6728 if (CORE.Input.eventWorker[i].threadId != 0)
6729 {
6730 TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i);
6731 pthread_cancel(CORE.Input.eventWorker[i].threadId);
6732 close(CORE.Input.eventWorker[i].fd);
6733 }
6734 }
6735 }
6736 #endif
6737 }
6738 else close(fd); // We are not interested in this device
6739 //-------------------------------------------------------------------------------------------------------
6740 }
6741
6742 static void PollKeyboardEvents(void)
6743 {
6744 // Scancode to keycode mapping for US keyboards
6745 // TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard:
6746 // Currently non US keyboards will have the wrong mapping for some keys
6747 static const int keymapUS[] = {
6748 0, 256, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 259, 258, 81, 87, 69, 82, 84,
6749 89, 85, 73, 79, 80, 91, 93, 257, 341, 65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 96,
6750 340, 92, 90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 344, 332, 342, 32, 280, 290, 291,
6751 292, 293, 294, 295, 296, 297, 298, 299, 282, 281, 327, 328, 329, 333, 324, 325,
6752 326, 334, 321, 322, 323, 320, 330, 0, 85, 86, 300, 301, 89, 90, 91, 92, 93, 94, 95,
6753 335, 345, 331, 283, 346, 101, 268, 265, 266, 263, 262, 269, 264, 267, 260, 261,
6754 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 347, 127,
6755 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
6756 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
6757 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
6758 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
6759 192, 193, 194, 0, 0, 0, 0, 0, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
6760 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
6761 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
6762 243, 244, 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0
6763 };
6764
6765 int fd = CORE.Input.Keyboard.fd;
6766 if (fd == -1) return;
6767
6768 struct input_event event = { 0 };
6769 int keycode = -1;
6770
6771 // Try to read data from the keyboard and only continue if successful
6772 while (read(fd, &event, sizeof(event)) == (int)sizeof(event))
6773 {
6774 // Button parsing
6775 if (event.type == EV_KEY)
6776 {
6777 #if defined(SUPPORT_SSH_KEYBOARD_RPI)
6778 // Change keyboard mode to events
6779 CORE.Input.Keyboard.evtMode = true;
6780 #endif
6781 // Keyboard button parsing
6782 if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255
6783 {
6784 keycode = keymapUS[event.code & 0xFF]; // The code we get is a scancode so we look up the appropriate keycode
6785
6786 // Make sure we got a valid keycode
6787 if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState)))
6788 {
6789 // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt
6790 // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL,
6791 // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat
6792 CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0;
6793 if (event.value >= 1)
6794 {
6795 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed
6796 CORE.Input.Keyboard.keyPressedQueueCount++;
6797 }
6798
6799 #if defined(SUPPORT_SCREEN_CAPTURE)
6800 // Check screen capture key (raylib key: KEY_F12)
6801 if (CORE.Input.Keyboard.currentKeyState[301] == 1)
6802 {
6803 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
6804 screenshotCounter++;
6805 }
6806 #endif
6807
6808 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
6809
6810 TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode);
6811 }
6812 }
6813 }
6814 }
6815 }
6816
6817 // Input device events reading thread
6818 static void *EventThread(void *arg)
6819 {
6820 struct input_event event = { 0 };
6821 InputEventWorker *worker = (InputEventWorker *)arg;
6822
6823 int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE
6824 bool gestureUpdate = false; // Flag to note gestures require to update
6825
6826 while (!CORE.Window.shouldClose)
6827 {
6828 // Try to read data from the device and only continue if successful
6829 while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event))
6830 {
6831 // Relative movement parsing
6832 if (event.type == EV_REL)
6833 {
6834 if (event.code == REL_X)
6835 {
6836 CORE.Input.Mouse.currentPosition.x += event.value;
6837 CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x;
6838
6839 touchAction = 2; // TOUCH_ACTION_MOVE
6840 gestureUpdate = true;
6841 }
6842
6843 if (event.code == REL_Y)
6844 {
6845 CORE.Input.Mouse.currentPosition.y += event.value;
6846 CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y;
6847
6848 touchAction = 2; // TOUCH_ACTION_MOVE
6849 gestureUpdate = true;
6850 }
6851
6852 if (event.code == REL_WHEEL) CORE.Input.Mouse.eventWheelMove.y += event.value;
6853 }
6854
6855 // Absolute movement parsing
6856 if (event.type == EV_ABS)
6857 {
6858 // Basic movement
6859 if (event.code == ABS_X)
6860 {
6861 CORE.Input.Mouse.currentPosition.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange
6862 CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange
6863
6864 touchAction = 2; // TOUCH_ACTION_MOVE
6865 gestureUpdate = true;
6866 }
6867
6868 if (event.code == ABS_Y)
6869 {
6870 CORE.Input.Mouse.currentPosition.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange
6871 CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange
6872
6873 touchAction = 2; // TOUCH_ACTION_MOVE
6874 gestureUpdate = true;
6875 }
6876
6877 // Multitouch movement
6878 if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events
6879
6880 if (event.code == ABS_MT_POSITION_X)
6881 {
6882 if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange
6883 }
6884
6885 if (event.code == ABS_MT_POSITION_Y)
6886 {
6887 if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange
6888 }
6889
6890 if (event.code == ABS_MT_TRACKING_ID)
6891 {
6892 if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS))
6893 {
6894 // Touch has ended for this point
6895 CORE.Input.Touch.position[worker->touchSlot].x = -1;
6896 CORE.Input.Touch.position[worker->touchSlot].y = -1;
6897 }
6898 }
6899
6900 // Touchscreen tap
6901 if (event.code == ABS_PRESSURE)
6902 {
6903 int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT];
6904
6905 if (!event.value && previousMouseLeftButtonState)
6906 {
6907 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0;
6908
6909 touchAction = 0; // TOUCH_ACTION_UP
6910 gestureUpdate = true;
6911 }
6912
6913 if (event.value && !previousMouseLeftButtonState)
6914 {
6915 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1;
6916
6917 touchAction = 1; // TOUCH_ACTION_DOWN
6918 gestureUpdate = true;
6919 }
6920 }
6921
6922 }
6923
6924 // Button parsing
6925 if (event.type == EV_KEY)
6926 {
6927 // Mouse button parsing
6928 if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT))
6929 {
6930 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value;
6931
6932 if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN
6933 else touchAction = 0; // TOUCH_ACTION_UP
6934 gestureUpdate = true;
6935 }
6936
6937 if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value;
6938 if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value;
6939 if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value;
6940 if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value;
6941 if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value;
6942 if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value;
6943 }
6944
6945 // Screen confinement
6946 if (!CORE.Input.Mouse.cursorHidden)
6947 {
6948 if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0;
6949 if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x;
6950
6951 if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0;
6952 if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y;
6953 }
6954
6955 // Update touch point count
6956 CORE.Input.Touch.pointCount = 0;
6957 if (CORE.Input.Touch.position[0].x >= 0) CORE.Input.Touch.pointCount++;
6958 if (CORE.Input.Touch.position[1].x >= 0) CORE.Input.Touch.pointCount++;
6959 if (CORE.Input.Touch.position[2].x >= 0) CORE.Input.Touch.pointCount++;
6960 if (CORE.Input.Touch.position[3].x >= 0) CORE.Input.Touch.pointCount++;
6961
6962 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_RPI, PLATFORM_DRM
6963 if (gestureUpdate)
6964 {
6965 GestureEvent gestureEvent = { 0 };
6966
6967 gestureEvent.touchAction = touchAction;
6968 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
6969
6970 gestureEvent.pointId[0] = 0;
6971 gestureEvent.pointId[1] = 1;
6972 gestureEvent.pointId[2] = 2;
6973 gestureEvent.pointId[3] = 3;
6974
6975 gestureEvent.position[0] = CORE.Input.Touch.position[0];
6976 gestureEvent.position[1] = CORE.Input.Touch.position[1];
6977 gestureEvent.position[2] = CORE.Input.Touch.position[2];
6978 gestureEvent.position[3] = CORE.Input.Touch.position[3];
6979
6980 ProcessGestureEvent(gestureEvent);
6981 }
6982 #endif
6983 }
6984
6985 WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time
6986 }
6987
6988 close(worker->fd);
6989
6990 return NULL;
6991 }
6992
6993 // Initialize gamepad system
6994 static void InitGamepad(void)
6995 {
6996 char gamepadDev[128] = { 0 };
6997
6998 for (int i = 0; i < MAX_GAMEPADS; i++)
6999 {
7000 sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i);
7001
7002 if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0)
7003 {
7004 // NOTE: Only show message for first gamepad
7005 if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available");
7006 }
7007 else
7008 {
7009 CORE.Input.Gamepad.ready[i] = true;
7010
7011 // NOTE: Only create one thread
7012 if (i == 0)
7013 {
7014 int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL);
7015
7016 if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread");
7017 else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully");
7018 }
7019 }
7020 }
7021 }
7022
7023 // Process Gamepad (/dev/input/js0)
7024 static void *GamepadThread(void *arg)
7025 {
7026 #define JS_EVENT_BUTTON 0x01 // Button pressed/released
7027 #define JS_EVENT_AXIS 0x02 // Joystick axis moved
7028 #define JS_EVENT_INIT 0x80 // Initial state of device
7029
7030 struct js_event {
7031 unsigned int time; // event timestamp in milliseconds
7032 short value; // event value
7033 unsigned char type; // event type
7034 unsigned char number; // event axis/button number
7035 };
7036
7037 // Read gamepad event
7038 struct js_event gamepadEvent = { 0 };
7039
7040 while (!CORE.Window.shouldClose)
7041 {
7042 for (int i = 0; i < MAX_GAMEPADS; i++)
7043 {
7044 if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event))
7045 {
7046 gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events
7047
7048 // Process gamepad events by type
7049 if (gamepadEvent.type == JS_EVENT_BUTTON)
7050 {
7051 //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value);
7052
7053 if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS)
7054 {
7055 // 1 - button pressed, 0 - button released
7056 CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value;
7057
7058 if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number;
7059 else CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
7060 }
7061 }
7062 else if (gamepadEvent.type == JS_EVENT_AXIS)
7063 {
7064 //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value);
7065
7066 if (gamepadEvent.number < MAX_GAMEPAD_AXIS)
7067 {
7068 // NOTE: Scaling of gamepadEvent.value to get values between -1..1
7069 CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768;
7070 }
7071 }
7072 }
7073 else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time
7074 }
7075 }
7076
7077 return NULL;
7078 }
7079 #endif // PLATFORM_RPI || PLATFORM_DRM
7080
7081 #if defined(PLATFORM_DRM)
7082 // Search matching DRM mode in connector's mode list
7083 static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode)
7084 {
7085 if (NULL == connector) return -1;
7086 if (NULL == mode) return -1;
7087
7088 // safe bitwise comparison of two modes
7089 #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b))
7090
7091 for (size_t i = 0; i < connector->count_modes; i++)
7092 {
7093 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay,
7094 connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive");
7095
7096 if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i;
7097 }
7098
7099 return -1;
7100
7101 #undef BINCMP
7102 }
7103
7104 // Search exactly matching DRM connector mode in connector's list
7105 static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
7106 {
7107 TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no");
7108
7109 if (NULL == connector) return -1;
7110
7111 for (int i = 0; i < CORE.Window.connector->count_modes; i++)
7112 {
7113 const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i];
7114
7115 TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive");
7116
7117 if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue;
7118
7119 if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i;
7120 }
7121
7122 TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found");
7123 return -1;
7124 }
7125
7126 // Search the nearest matching DRM connector mode in connector's list
7127 static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
7128 {
7129 TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no");
7130
7131 if (NULL == connector) return -1;
7132
7133 int nearestIndex = -1;
7134 for (int i = 0; i < CORE.Window.connector->count_modes; i++)
7135 {
7136 const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i];
7137
7138 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh,
7139 (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive");
7140
7141 if ((mode->hdisplay < width) || (mode->vdisplay < height))
7142 {
7143 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small");
7144 continue;
7145 }
7146
7147 if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced))
7148 {
7149 TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode");
7150 continue;
7151 }
7152
7153 if (nearestIndex < 0)
7154 {
7155 nearestIndex = i;
7156 continue;
7157 }
7158
7159 const int widthDiff = abs(mode->hdisplay - width);
7160 const int heightDiff = abs(mode->vdisplay - height);
7161 const int fpsDiff = abs(mode->vrefresh - fps);
7162
7163 const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width);
7164 const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height);
7165 const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps);
7166
7167 if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) {
7168 nearestIndex = i;
7169 }
7170 }
7171
7172 return nearestIndex;
7173 }
7174 #endif
7175
7176 #if defined(SUPPORT_EVENTS_AUTOMATION)
7177 // NOTE: Loading happens over AutomationEvent *events
7178 // TODO: This system should probably be redesigned
7179 static void LoadAutomationEvents(const char *fileName)
7180 {
7181 // Load events file (binary)
7182 /*
7183 FILE *repFile = fopen(fileName, "rb");
7184 unsigned char fileId[4] = { 0 };
7185
7186 fread(fileId, 1, 4, repFile);
7187
7188 if ((fileId[0] == 'r') && (fileId[1] == 'E') && (fileId[2] == 'P') && (fileId[1] == ' '))
7189 {
7190 fread(&eventCount, sizeof(int), 1, repFile);
7191 TRACELOG(LOG_WARNING, "Events loaded: %i\n", eventCount);
7192 fread(events, sizeof(AutomationEvent), eventCount, repFile);
7193 }
7194
7195 fclose(repFile);
7196 */
7197
7198 // Load events file (text)
7199 FILE *repFile = fopen(fileName, "rt");
7200
7201 if (repFile != NULL)
7202 {
7203 unsigned int count = 0;
7204 char buffer[256] = { 0 };
7205
7206 fgets(buffer, 256, repFile);
7207
7208 while (!feof(repFile))
7209 {
7210 if (buffer[0] == 'c') sscanf(buffer, "c %i", &eventCount);
7211 else if (buffer[0] == 'e')
7212 {
7213 sscanf(buffer, "e %d %d %d %d %d", &events[count].frame, &events[count].type,
7214 &events[count].params[0], &events[count].params[1], &events[count].params[2]);
7215
7216 count++;
7217 }
7218
7219 fgets(buffer, 256, repFile);
7220 }
7221
7222 if (count != eventCount) TRACELOG(LOG_WARNING, "Events count provided is different than count");
7223
7224 fclose(repFile);
7225 }
7226
7227 TRACELOG(LOG_WARNING, "Events loaded: %i", eventCount);
7228 }
7229
7230 // Export recorded events into a file
7231 static void ExportAutomationEvents(const char *fileName)
7232 {
7233 unsigned char fileId[4] = "rEP ";
7234
7235 // Save as binary
7236 /*
7237 FILE *repFile = fopen(fileName, "wb");
7238 fwrite(fileId, sizeof(unsigned char), 4, repFile);
7239 fwrite(&eventCount, sizeof(int), 1, repFile);
7240 fwrite(events, sizeof(AutomationEvent), eventCount, repFile);
7241 fclose(repFile);
7242 */
7243
7244 // Export events as text
7245 FILE *repFile = fopen(fileName, "wt");
7246
7247 if (repFile != NULL)
7248 {
7249 fprintf(repFile, "# Automation events list\n");
7250 fprintf(repFile, "# c <events_count>\n");
7251 fprintf(repFile, "# e <frame> <event_type> <param0> <param1> <param2> // <event_type_name>\n");
7252
7253 fprintf(repFile, "c %i\n", eventCount);
7254 for (int i = 0; i < eventCount; i++)
7255 {
7256 fprintf(repFile, "e %i %i %i %i %i // %s\n", events[i].frame, events[i].type,
7257 events[i].params[0], events[i].params[1], events[i].params[2], autoEventTypeName[events[i].type]);
7258 }
7259
7260 fclose(repFile);
7261 }
7262 }
7263
7264 // EndDrawing() -> After PollInputEvents()
7265 // Check event in current frame and save into the events[i] array
7266 static void RecordAutomationEvent(unsigned int frame)
7267 {
7268 for (int key = 0; key < MAX_KEYBOARD_KEYS; key++)
7269 {
7270 // INPUT_KEY_UP (only saved once)
7271 if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key])
7272 {
7273 events[eventCount].frame = frame;
7274 events[eventCount].type = INPUT_KEY_UP;
7275 events[eventCount].params[0] = key;
7276 events[eventCount].params[1] = 0;
7277 events[eventCount].params[2] = 0;
7278
7279 TRACELOG(LOG_INFO, "[%i] INPUT_KEY_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7280 eventCount++;
7281 }
7282
7283 // INPUT_KEY_DOWN
7284 if (CORE.Input.Keyboard.currentKeyState[key])
7285 {
7286 events[eventCount].frame = frame;
7287 events[eventCount].type = INPUT_KEY_DOWN;
7288 events[eventCount].params[0] = key;
7289 events[eventCount].params[1] = 0;
7290 events[eventCount].params[2] = 0;
7291
7292 TRACELOG(LOG_INFO, "[%i] INPUT_KEY_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7293 eventCount++;
7294 }
7295 }
7296
7297 for (int button = 0; button < MAX_MOUSE_BUTTONS; button++)
7298 {
7299 // INPUT_MOUSE_BUTTON_UP
7300 if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button])
7301 {
7302 events[eventCount].frame = frame;
7303 events[eventCount].type = INPUT_MOUSE_BUTTON_UP;
7304 events[eventCount].params[0] = button;
7305 events[eventCount].params[1] = 0;
7306 events[eventCount].params[2] = 0;
7307
7308 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7309 eventCount++;
7310 }
7311
7312 // INPUT_MOUSE_BUTTON_DOWN
7313 if (CORE.Input.Mouse.currentButtonState[button])
7314 {
7315 events[eventCount].frame = frame;
7316 events[eventCount].type = INPUT_MOUSE_BUTTON_DOWN;
7317 events[eventCount].params[0] = button;
7318 events[eventCount].params[1] = 0;
7319 events[eventCount].params[2] = 0;
7320
7321 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7322 eventCount++;
7323 }
7324 }
7325
7326 // INPUT_MOUSE_POSITION (only saved if changed)
7327 if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) ||
7328 ((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y))
7329 {
7330 events[eventCount].frame = frame;
7331 events[eventCount].type = INPUT_MOUSE_POSITION;
7332 events[eventCount].params[0] = (int)CORE.Input.Mouse.currentPosition.x;
7333 events[eventCount].params[1] = (int)CORE.Input.Mouse.currentPosition.y;
7334 events[eventCount].params[2] = 0;
7335
7336 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7337 eventCount++;
7338 }
7339
7340 // INPUT_MOUSE_WHEEL_MOTION
7341 if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) ||
7342 ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y))
7343 {
7344 events[eventCount].frame = frame;
7345 events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION;
7346 events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x;
7347 events[eventCount].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;;
7348 events[eventCount].params[2] = 0;
7349
7350 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_WHEEL_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7351 eventCount++;
7352 }
7353
7354 for (int id = 0; id < MAX_TOUCH_POINTS; id++)
7355 {
7356 // INPUT_TOUCH_UP
7357 if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id])
7358 {
7359 events[eventCount].frame = frame;
7360 events[eventCount].type = INPUT_TOUCH_UP;
7361 events[eventCount].params[0] = id;
7362 events[eventCount].params[1] = 0;
7363 events[eventCount].params[2] = 0;
7364
7365 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7366 eventCount++;
7367 }
7368
7369 // INPUT_TOUCH_DOWN
7370 if (CORE.Input.Touch.currentTouchState[id])
7371 {
7372 events[eventCount].frame = frame;
7373 events[eventCount].type = INPUT_TOUCH_DOWN;
7374 events[eventCount].params[0] = id;
7375 events[eventCount].params[1] = 0;
7376 events[eventCount].params[2] = 0;
7377
7378 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7379 eventCount++;
7380 }
7381
7382 // INPUT_TOUCH_POSITION
7383 // TODO: It requires the id!
7384 /*
7385 if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) ||
7386 ((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y))
7387 {
7388 events[eventCount].frame = frame;
7389 events[eventCount].type = INPUT_TOUCH_POSITION;
7390 events[eventCount].params[0] = id;
7391 events[eventCount].params[1] = (int)CORE.Input.Touch.currentPosition[id].x;
7392 events[eventCount].params[2] = (int)CORE.Input.Touch.currentPosition[id].y;
7393
7394 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7395 eventCount++;
7396 }
7397 */
7398 }
7399
7400 for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++)
7401 {
7402 // INPUT_GAMEPAD_CONNECT
7403 /*
7404 if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
7405 (CORE.Input.Gamepad.currentState[gamepad] == true)) // Check if changed to ready
7406 {
7407 // TODO: Save gamepad connect event
7408 }
7409 */
7410
7411 // INPUT_GAMEPAD_DISCONNECT
7412 /*
7413 if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
7414 (CORE.Input.Gamepad.currentState[gamepad] == false)) // Check if changed to not-ready
7415 {
7416 // TODO: Save gamepad disconnect event
7417 }
7418 */
7419
7420 for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++)
7421 {
7422 // INPUT_GAMEPAD_BUTTON_UP
7423 if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button])
7424 {
7425 events[eventCount].frame = frame;
7426 events[eventCount].type = INPUT_GAMEPAD_BUTTON_UP;
7427 events[eventCount].params[0] = gamepad;
7428 events[eventCount].params[1] = button;
7429 events[eventCount].params[2] = 0;
7430
7431 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7432 eventCount++;
7433 }
7434
7435 // INPUT_GAMEPAD_BUTTON_DOWN
7436 if (CORE.Input.Gamepad.currentButtonState[gamepad][button])
7437 {
7438 events[eventCount].frame = frame;
7439 events[eventCount].type = INPUT_GAMEPAD_BUTTON_DOWN;
7440 events[eventCount].params[0] = gamepad;
7441 events[eventCount].params[1] = button;
7442 events[eventCount].params[2] = 0;
7443
7444 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7445 eventCount++;
7446 }
7447 }
7448
7449 for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++)
7450 {
7451 // INPUT_GAMEPAD_AXIS_MOTION
7452 if (CORE.Input.Gamepad.axisState[gamepad][axis] > 0.1f)
7453 {
7454 events[eventCount].frame = frame;
7455 events[eventCount].type = INPUT_GAMEPAD_AXIS_MOTION;
7456 events[eventCount].params[0] = gamepad;
7457 events[eventCount].params[1] = axis;
7458 events[eventCount].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f);
7459
7460 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_AXIS_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7461 eventCount++;
7462 }
7463 }
7464 }
7465
7466 // INPUT_GESTURE
7467 if (GESTURES.current != GESTURE_NONE)
7468 {
7469 events[eventCount].frame = frame;
7470 events[eventCount].type = INPUT_GESTURE;
7471 events[eventCount].params[0] = GESTURES.current;
7472 events[eventCount].params[1] = 0;
7473 events[eventCount].params[2] = 0;
7474
7475 TRACELOG(LOG_INFO, "[%i] INPUT_GESTURE: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
7476 eventCount++;
7477 }
7478 }
7479
7480 // Play automation event
7481 static void PlayAutomationEvent(unsigned int frame)
7482 {
7483 for (unsigned int i = 0; i < eventCount; i++)
7484 {
7485 if (events[i].frame == frame)
7486 {
7487 switch (events[i].type)
7488 {
7489 // Input events
7490 case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = false; break; // param[0]: key
7491 case INPUT_KEY_DOWN: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = true; break; // param[0]: key
7492 case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = false; break; // param[0]: key
7493 case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = true; break; // param[0]: key
7494 case INPUT_MOUSE_POSITION: // param[0]: x, param[1]: y
7495 {
7496 CORE.Input.Mouse.currentPosition.x = (float)events[i].params[0];
7497 CORE.Input.Mouse.currentPosition.y = (float)events[i].params[1];
7498 } break;
7499 case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta
7500 {
7501 CORE.Input.Mouse.currentWheelMove.x = (float)events[i].params[0]; break;
7502 CORE.Input.Mouse.currentWheelMove.y = (float)events[i].params[1]; break;
7503 } break;
7504 case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[events[i].params[0]] = false; break; // param[0]: id
7505 case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[events[i].params[0]] = true; break; // param[0]: id
7506 case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y
7507 {
7508 CORE.Input.Touch.position[events[i].params[0]].x = (float)events[i].params[1];
7509 CORE.Input.Touch.position[events[i].params[0]].y = (float)events[i].params[2];
7510 } break;
7511 case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = true; break; // param[0]: gamepad
7512 case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = false; break; // param[0]: gamepad
7513 case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = false; break; // param[0]: gamepad, param[1]: button
7514 case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = true; break; // param[0]: gamepad, param[1]: button
7515 case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta
7516 {
7517 CORE.Input.Gamepad.axisState[events[i].params[0]][events[i].params[1]] = ((float)events[i].params[2]/32768.0f);
7518 } break;
7519 case INPUT_GESTURE: GESTURES.current = events[i].params[0]; break; // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current
7520
7521 // Window events
7522 case WINDOW_CLOSE: CORE.Window.shouldClose = true; break;
7523 case WINDOW_MAXIMIZE: MaximizeWindow(); break;
7524 case WINDOW_MINIMIZE: MinimizeWindow(); break;
7525 case WINDOW_RESIZE: SetWindowSize(events[i].params[0], events[i].params[1]); break;
7526
7527 // Custom events
7528 case ACTION_TAKE_SCREENSHOT:
7529 {
7530 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
7531 screenshotCounter++;
7532 } break;
7533 case ACTION_SETTARGETFPS: SetTargetFPS(events[i].params[0]); break;
7534 default: break;
7535 }
7536 }
7537 }
7538 }
7539 #endif
7540
7541 #if !defined(SUPPORT_MODULE_RTEXT)
7542 // Formatting of text with variables to 'embed'
7543 // WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times
7544 const char *TextFormat(const char *text, ...)
7545 {
7546 #ifndef MAX_TEXTFORMAT_BUFFERS
7547 #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting
7548 #endif
7549 #ifndef MAX_TEXT_BUFFER_LENGTH
7550 #define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer
7551 #endif
7552
7553 // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations
7554 static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 };
7555 static int index = 0;
7556
7557 char *currentBuffer = buffers[index];
7558 memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using
7559
7560 va_list args;
7561 va_start(args, text);
7562 vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args);
7563 va_end(args);
7564
7565 index += 1; // Move to next buffer for next function call
7566 if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0;
7567
7568 return currentBuffer;
7569 }
7570 #endif // !SUPPORT_MODULE_RTEXT
7571