Logo

index : raylib-jai

---

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/raylib-jai.git/html/Raylib/raylib/src/platforms/rcore_drm.c blob: 366477aac74464b650512453f1ac9ece2f2afa0d [raw] [clear marker]

        
0/**********************************************************************************************
1*
2* rcore_drm - Functions to manage window, graphics device and inputs
3*
4* PLATFORM: DRM
5* - Raspberry Pi 0-5 (DRM/KMS)
6* - Linux DRM subsystem (KMS mode)
7*
8* LIMITATIONS:
9* - Most of the window/monitor functions are not implemented (not required)
10*
11* POSSIBLE IMPROVEMENTS:
12* - Improvement 01
13* - Improvement 02
14*
15* ADDITIONAL NOTES:
16* - TRACELOG() function is located in raylib [utils] module
17*
18* CONFIGURATION:
19* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only)
20* Reconfigure standard input to receive key inputs, works with SSH connection
21* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other
22* running processes orblocking the device if not restored properly. Use with care
23*
24* DEPENDENCIES:
25* - DRM and GLM: System libraries for display initialization and configuration
26* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs)
27*
28*
29* LICENSE: zlib/libpng
30*
31* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors
32*
33* This software is provided "as-is", without any express or implied warranty. In no event
34* will the authors be held liable for any damages arising from the use of this software.
35*
36* Permission is granted to anyone to use this software for any purpose, including commercial
37* applications, and to alter it and redistribute it freely, subject to the following restrictions:
38*
39* 1. The origin of this software must not be misrepresented; you must not claim that you
40* wrote the original software. If you use this software in a product, an acknowledgment
41* in the product documentation would be appreciated but is not required.
42*
43* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
44* as being the original software.
45*
46* 3. This notice may not be removed or altered from any source distribution.
47*
48**********************************************************************************************/
49
50#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl()
51#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO
52#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr()
53#include <pthread.h> // POSIX threads management (inputs reading)
54#include <dirent.h> // POSIX directory browsing
55#include <limits.h> // INT_MAX
56
57#include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations
58#include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition
59#include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...)
60#include <linux/joystick.h> // Linux: Joystick support library
61
62// WARNING: Both 'linux/input.h' and 'raylib.h' define KEY_F12
63// To avoid conflict with the capturing code in rcore.c we undefine the macro KEY_F12,
64// so the enum KEY_F12 from raylib is used
65#undef KEY_F12
66
67#include <xf86drm.h> // Direct Rendering Manager user-level library interface
68#include <xf86drmMode.h> // Direct Rendering Manager mode setting (KMS) interface
69
70#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
71 #include <gbm.h> // Generic Buffer Management (native platform for EGL on DRM)
72 #include "EGL/egl.h" // Native platform windowing system interface
73 #include "EGL/eglext.h" // EGL extensions
74#else
75 #include <sys/mman.h> // For mmap when copying to the dumb buffer
76 #include <errno.h> // For the conversion of certain error messages
77#endif
78
79// NOTE: DRM cache enables triple buffered DRM caching
80#if defined(SUPPORT_DRM_CACHE)
81 #include <poll.h> // Required for: drmHandleEvent() poll
82 #include <errno.h> // Required for: EBUSY, EAGAIN
83
84 #define MAX_DRM_CACHED_BUFFERS 3
85#endif // SUPPORT_DRM_CACHE
86
87#ifndef EGL_OPENGL_ES3_BIT
88 #define EGL_OPENGL_ES3_BIT 0x40
89#endif
90
91//----------------------------------------------------------------------------------
92// Defines and Macros
93//----------------------------------------------------------------------------------
94#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number
95
96#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events
97
98// Actually biggest key is KEY_CNT but we only really map the keys up to KEY_ALS_TOGGLE
99#define KEYMAP_SIZE KEY_ALS_TOGGLE
100
101//----------------------------------------------------------------------------------
102// Types and Structures Definition
103//----------------------------------------------------------------------------------
104typedef struct {
105 // Display data
106 int fd; // File descriptor for /dev/dri/...
107 drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector
108 drmModeCrtc *crtc; // CRT Controller
109 int modeIndex; // Index of the used mode of connector->modes
110 uint32_t prevFB; // Previous DRM framebufer (during frame swapping)
111
112#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
113 struct gbm_device *gbmDevice; // GBM device
114 struct gbm_surface *gbmSurface; // GBM surface
115 struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping)
116 EGLDisplay device; // Native display device (physical screen connection)
117 EGLSurface surface; // Surface to draw on, framebuffers (connected to context)
118 EGLContext context; // Graphic context, mode in which drawing can be done
119 EGLConfig config; // Graphic config
120#else
121 uint32_t prevDumbHandle; // Handle to the previous dumb buffer (during frame swapping)
122#endif
123
124 // Keyboard data
125 int defaultKeyboardMode; // Default keyboard mode
126 bool eventKeyboardMode; // Keyboard in event mode
127 int defaultFileFlags; // Default IO file flags
128 struct termios defaultSettings; // Default keyboard settings
129 int keyboardFd; // File descriptor for the evdev keyboard
130
131 // Mouse data
132 Vector2 eventWheelMove; // Registers the event mouse wheel variation
133 // NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update
134 char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab
135 bool cursorRelative; // Relative cursor mode
136 int mouseFd; // File descriptor for the evdev mouse/touch/gestures
137 Rectangle absRange; // Range of values for absolute pointing devices (touchscreens)
138 int touchSlot; // Hold the touch slot number of the currently being sent multitouch block
139
140 // Gamepad data
141 int gamepadStreamFd[MAX_GAMEPADS]; // Gamepad device file descriptor
142 int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXES][2]; // [0] = min, [1] = range value of the axes
143 int gamepadAbsAxisMap[MAX_GAMEPADS][ABS_CNT]; // Maps the axes gamepads from the evdev api to a sequential one
144 int gamepadCount; // The number of gamepads registered
145} PlatformData;
146
147#if defined(SUPPORT_DRM_CACHE)
148typedef struct {
149 struct gbm_bo *bo; // Graphics buffer object
150 uint32_t fbId; // DRM framebuffer ID
151} FramebufferCache;
152
153static FramebufferCache fbCache[MAX_DRM_CACHED_BUFFERS] = { 0 };
154static volatile int fbCacheCount = 0;
155static volatile bool pendingFlip = false;
156static bool crtcSet = false;
157#endif // SUPPORT_DRM_CACHE
158
159//----------------------------------------------------------------------------------
160// Global Variables Definition
161//----------------------------------------------------------------------------------
162extern CoreData CORE; // Global CORE state context
163
164static PlatformData platform = { 0 }; // Platform specific data
165
166//----------------------------------------------------------------------------------
167// Global Variables Definition
168//----------------------------------------------------------------------------------
169
170// NOTE: The complete evdev EV_KEY list can be found at /usr/include/linux/input-event-codes.h
171// TODO: Complete the LUT with all unicode decimal values
172// TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard:
173// Currently non US keyboards will have the wrong mapping for some keys
174// NOTE: Replacing this with the keymap from X11 would probably be useless, as people use the drm
175// backend to *avoid* X11
176static const int evkeyToUnicodeLUT[] = {
177 0, 27, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 8, 0, 113, 119, 101, 114,
178 116, 121, 117, 105, 111, 112, 0, 0, 13, 0, 97, 115, 100, 102, 103, 104, 106, 107, 108, 59,
179 39, 96, 0, 92, 122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 32
180 // LUT currently incomplete, just mapped the most essential keys
181};
182
183// This is the map used to map any keycode returned from linux to a raylib code from 'raylib.h'
184// NOTE: Use short here to save a little memory
185static const short linuxToRaylibMap[KEYMAP_SIZE] = {
186 // We don't map those with designated initialization, because we would getting
187 // into loads of naming conflicts
188 0, 256, 49, 50, 51, 52, 53, 54,
189 55, 56, 57, 48, 45, 61, 259, 258,
190 81, 87, 69, 82, 84, 89, 85, 73,
191 79, 80, 91, 93, 257, 341, 65, 83,
192 68, 70, 71, 72, 74, 75, 76, 59,
193 39, 96, 340, 92, 90, 88, 67, 86,
194 66, 78, 77, 44, 46, 47, 344, 332,
195 342, 32, 280, 290, 291, 292, 293, 294,
196 295, 296, 297, 298, 299, 282, 281, 327,
197 328, 329, 333, 324, 325, 326, 334, 321,
198 322, 323, 320, 330, 0, 85, 86, 300,
199 301, 89, 90, 91, 92, 93, 94, 95,
200 335, 345, 331, 283, 346, 101, 268, 265,
201 266, 263, 262, 269, 264, 267, 260, 261,
202 112, 113, 114, 115, 116, 117, 118, 119,
203 120, 121, 122, 123, 124, 125, 347, 127,
204 128, 129, 130, 131, 132, 133, 134, 135,
205 136, 137, 138, 139, 140, 141, 142, 143,
206 144, 145, 146, 147, 148, 149, 150, 151,
207 152, 153, 154, 155, 156, 157, 158, 159,
208 160, 161, 162, 163, 164, 165, 166, 167,
209 168, 169, 170, 171, 172, 173, 174, 175,
210 176, 177, 178, 179, 180, 181, 182, 183,
211 184, 185, 186, 187, 188, 189, 190, 191,
212 192, 193, 194, 0, 0, 0, 0, 0,
213 200, 201, 202, 203, 204, 205, 206, 207,
214 208, 209, 210, 211, 212, 213, 214, 215,
215 216, 217, 218, 219, 220, 221, 222, 223,
216 224, 225, 226, 227, 228, 229, 230, 231,
217 232, 233, 234, 235, 236, 237, 238, 239,
218 240, 241, 242, 243, 244, 245, 246, 247,
219 248, 0, 0, 0, 0, 0, 0, 0,
220
221 // Gamepads are mapped according to:
222 // REF: https://www.kernel.org/doc/html/next/input/gamepad.html
223 // Those mappings are standardized, but that doesn't mean people follow
224 // the standards, so this is more of an approximation
225 [BTN_DPAD_UP] = GAMEPAD_BUTTON_LEFT_FACE_UP,
226 [BTN_DPAD_RIGHT] = GAMEPAD_BUTTON_LEFT_FACE_RIGHT,
227 [BTN_DPAD_DOWN] = GAMEPAD_BUTTON_LEFT_FACE_DOWN,
228 [BTN_DPAD_LEFT] = GAMEPAD_BUTTON_LEFT_FACE_LEFT,
229 [BTN_Y] = GAMEPAD_BUTTON_RIGHT_FACE_UP,
230 [BTN_B] = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT,
231 [BTN_A] = GAMEPAD_BUTTON_RIGHT_FACE_DOWN,
232 [BTN_X] = GAMEPAD_BUTTON_RIGHT_FACE_LEFT,
233 [BTN_TL] = GAMEPAD_BUTTON_LEFT_TRIGGER_1,
234 [BTN_TL2] = GAMEPAD_BUTTON_LEFT_TRIGGER_2,
235 [BTN_TR] = GAMEPAD_BUTTON_RIGHT_TRIGGER_1,
236 [BTN_TR2] = GAMEPAD_BUTTON_RIGHT_TRIGGER_2,
237 [BTN_SELECT] = GAMEPAD_BUTTON_MIDDLE_LEFT,
238 [BTN_MODE] = GAMEPAD_BUTTON_MIDDLE,
239 [BTN_START] = GAMEPAD_BUTTON_MIDDLE_RIGHT,
240 [BTN_THUMBL] = GAMEPAD_BUTTON_LEFT_THUMB,
241 [BTN_THUMBR] = GAMEPAD_BUTTON_RIGHT_THUMB,
242};
243
244//----------------------------------------------------------------------------------
245// Module Internal Functions Declaration
246//----------------------------------------------------------------------------------
247int InitPlatform(void); // Initialize platform (graphics, inputs and more)
248void ClosePlatform(void); // Close platform
249
250#if defined(SUPPORT_SSH_KEYBOARD_RPI)
251static void InitKeyboard(void); // Initialize raw keyboard system
252static void RestoreKeyboard(void); // Restore keyboard system
253static void ProcessKeyboard(void); // Process keyboard events
254#endif
255
256// Input management functions
257static void InitEvdevInput(void); // Initialize evdev inputs
258static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate
259static void PollKeyboardEvents(void); // Process evdev keyboard events
260static void PollGamepadEvents(void); // Process evdev gamepad events
261static void PollMouseEvents(void); // Process evdev mouse events
262
263static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list
264static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list
265static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list
266
267static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform())
268
269//----------------------------------------------------------------------------------
270// Module Functions Declaration
271//----------------------------------------------------------------------------------
272// NOTE: Functions declaration is provided by raylib.h
273
274//----------------------------------------------------------------------------------
275// Module Functions Definition: Window and Graphics Device
276//----------------------------------------------------------------------------------
277
278// Check if application should close
279// NOTE: By default, if KEY_ESCAPE pressed
280bool WindowShouldClose(void)
281{
282 if (CORE.Window.ready) return CORE.Window.shouldClose;
283 else return true;
284}
285
286// Toggle fullscreen mode
287void ToggleFullscreen(void)
288{
289 TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform");
290}
291
292// Toggle borderless windowed mode
293void ToggleBorderlessWindowed(void)
294{
295 TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform");
296}
297
298// Set window state: maximized, if resizable
299void MaximizeWindow(void)
300{
301 TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform");
302}
303
304// Set window state: minimized
305void MinimizeWindow(void)
306{
307 TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform");
308}
309
310// Restore window from being minimized/maximized
311void RestoreWindow(void)
312{
313 TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform");
314}
315
316// Set window configuration state using flags
317void SetWindowState(unsigned int flags)
318{
319 TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform");
320}
321
322// Clear window configuration state flags
323void ClearWindowState(unsigned int flags)
324{
325 TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform");
326}
327
328// Set icon for window
329void SetWindowIcon(Image image)
330{
331 TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform");
332}
333
334// Set icon for window
335void SetWindowIcons(Image *images, int count)
336{
337 TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform");
338}
339
340// Set title for window
341void SetWindowTitle(const char *title)
342{
343 CORE.Window.title = title;
344}
345
346// Set window position on screen (windowed mode)
347void SetWindowPosition(int x, int y)
348{
349 TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform");
350}
351
352// Set monitor for the current window
353void SetWindowMonitor(int monitor)
354{
355 TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform");
356}
357
358// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
359void SetWindowMinSize(int width, int height)
360{
361 CORE.Window.screenMin.width = width;
362 CORE.Window.screenMin.height = height;
363}
364
365// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE)
366void SetWindowMaxSize(int width, int height)
367{
368 CORE.Window.screenMax.width = width;
369 CORE.Window.screenMax.height = height;
370}
371
372// Set window dimensions
373void SetWindowSize(int width, int height)
374{
375 TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform");
376}
377
378// Set window opacity, value opacity is between 0.0 and 1.0
379void SetWindowOpacity(float opacity)
380{
381 TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform");
382}
383
384// Set window focused
385void SetWindowFocused(void)
386{
387 TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform");
388}
389
390// Get native window handle
391void *GetWindowHandle(void)
392{
393 TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform");
394 return NULL;
395}
396
397// Get number of monitors
398int GetMonitorCount(void)
399{
400 TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform");
401 return 1;
402}
403
404// Get current monitor where window is placed
405int GetCurrentMonitor(void)
406{
407 TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform");
408 return 0;
409}
410
411// Get selected monitor position
412Vector2 GetMonitorPosition(int monitor)
413{
414 TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform");
415 return (Vector2){ 0, 0 };
416}
417
418// Get selected monitor width (currently used by monitor)
419int GetMonitorWidth(int monitor)
420{
421 int width = 0;
422
423 if (monitor != 0)
424 {
425 TRACELOG(LOG_WARNING, "GetMonitorWidth() implemented for first monitor only");
426 }
427 else if ((platform.connector) && (platform.modeIndex >= 0))
428 {
429 width = platform.connector->modes[platform.modeIndex].hdisplay;
430 }
431
432 return width;
433}
434
435// Get selected monitor height (currently used by monitor)
436int GetMonitorHeight(int monitor)
437{
438 int height = 0;
439
440 if (monitor != 0)
441 {
442 TRACELOG(LOG_WARNING, "GetMonitorHeight() implemented for first monitor only");
443 }
444 else if ((platform.connector) && (platform.modeIndex >= 0))
445 {
446 height = platform.connector->modes[platform.modeIndex].vdisplay;
447 }
448
449 return height;
450}
451
452// Get selected monitor physical width in millimetres
453int GetMonitorPhysicalWidth(int monitor)
454{
455 int physicalWidth = 0;
456
457 if (monitor != 0)
458 {
459 TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() implemented for first monitor only");
460 }
461 else if ((platform.connector) && (platform.modeIndex >= 0))
462 {
463 physicalWidth = platform.connector->mmWidth;
464 }
465
466 return physicalWidth;
467}
468
469// Get selected monitor physical height in millimetres
470int GetMonitorPhysicalHeight(int monitor)
471{
472 int physicalHeight = 0;
473
474 if (monitor != 0)
475 {
476 TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() implemented for first monitor only");
477 }
478 else if ((platform.connector) && (platform.modeIndex >= 0))
479 {
480 physicalHeight = platform.connector->mmHeight;
481 }
482
483 return physicalHeight;
484}
485
486// Get selected monitor refresh rate
487int GetMonitorRefreshRate(int monitor)
488{
489 int refresh = 0;
490
491 if ((platform.connector) && (platform.modeIndex >= 0))
492 {
493 refresh = platform.connector->modes[platform.modeIndex].vrefresh;
494 }
495
496 return refresh;
497}
498
499// Get the human-readable, UTF-8 encoded name of the selected monitor
500const char *GetMonitorName(int monitor)
501{
502 const char *name = "";
503
504 if (monitor != 0)
505 {
506 TRACELOG(LOG_WARNING, "GetMonitorName() implemented for first monitor only");
507 }
508 else if ((platform.connector) && (platform.modeIndex >= 0))
509 {
510 name = platform.connector->modes[platform.modeIndex].name;
511 }
512
513 return name;
514}
515
516// Get window position XY on monitor
517Vector2 GetWindowPosition(void)
518{
519 return (Vector2){ 0, 0 };
520}
521
522// Get window scale DPI factor for current monitor
523Vector2 GetWindowScaleDPI(void)
524{
525 return (Vector2){ 1.0f, 1.0f };
526}
527
528// Set clipboard text content
529void SetClipboardText(const char *text)
530{
531 TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform");
532}
533
534// Get clipboard text content
535// NOTE: returned string is allocated and freed by GLFW
536const char *GetClipboardText(void)
537{
538 TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform");
539 return NULL;
540}
541
542// Get clipboard image
543Image GetClipboardImage(void)
544{
545 Image image = { 0 };
546
547 TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform");
548
549 return image;
550}
551
552// Show mouse cursor
553void ShowCursor(void)
554{
555 CORE.Input.Mouse.cursorHidden = false;
556}
557
558// Hides mouse cursor
559void HideCursor(void)
560{
561 CORE.Input.Mouse.cursorHidden = true;
562}
563
564// Enables cursor (unlock cursor)
565void EnableCursor(void)
566{
567 // Set cursor position in the middle
568 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
569
570 platform.cursorRelative = false;
571 CORE.Input.Mouse.cursorLocked = false;
572}
573
574// Disables cursor (lock cursor)
575void DisableCursor(void)
576{
577 // Set cursor position in the middle
578 SetMousePosition(0, 0);
579
580 platform.cursorRelative = true;
581 CORE.Input.Mouse.cursorLocked = true;
582}
583
584#if defined(SUPPORT_DRM_CACHE)
585
586// Destroy cached framebuffer callback, set by gbm_bo_set_user_data()
587static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data)
588{
589 uint32_t fbId = (uintptr_t)data;
590
591 // Remove from cache
592 for (int i = 0; i < fbCacheCount; i++)
593 {
594 if (fbCache[i].bo == bo)
595 {
596 TRACELOG(LOG_INFO, "DISPLAY: DRM: Framebuffer removed [%u]", (uintptr_t)fbId);
597 drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB
598
599 // Shift remaining entries
600 for (int j = i; j < fbCacheCount - 1; j++) fbCache[j] = fbCache[j + 1];
601
602 fbCacheCount--;
603 break;
604 }
605 }
606}
607
608// Create or retrieve cached DRM FB for BO
609static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo)
610{
611 // Try to find existing cache entry
612 for (int i = 0; i < fbCacheCount; i++)
613 {
614 if (fbCache[i].bo == bo) return fbCache[i].fbId;
615 }
616
617 // Create new entry if cache not full
618 if (fbCacheCount >= MAX_DRM_CACHED_BUFFERS) return 0; // FB cache full
619
620 uint32_t handle = gbm_bo_get_handle(bo).u32;
621 uint32_t stride = gbm_bo_get_stride(bo);
622 uint32_t width = gbm_bo_get_width(bo);
623 uint32_t height = gbm_bo_get_height(bo);
624
625 uint32_t fbId = 0;
626 if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) return 0;
627
628 // Store in cache
629 fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId };
630 fbCacheCount++;
631
632 // Set destroy callback to auto-cleanup
633 gbm_bo_set_user_data(bo, (void *)(uintptr_t)fbId, DestroyFrameBufferCallback);
634
635 TRACELOG(LOG_INFO, "DISPLAY: DRM: Added new buffer object [%u]" , (uintptr_t)fbId);
636
637 return fbId;
638}
639
640// Renders a blank frame to allocate initial buffers
641// TODO: WARNING: Platform backend should not include OpenGL code
642void RenderBlankFrame()
643{
644 glClearColor(0, 0, 0, 1);
645 glClear(GL_COLOR_BUFFER_BIT);
646 eglSwapBuffers(platform.device, platform.surface);
647 glFinish(); // Ensure the buffer is processed
648}
649
650// Initialize with first buffer only
651int InitSwapScreenBuffer()
652{
653 if (!platform.gbmSurface || (platform.fd < 0))
654 {
655 TRACELOG(LOG_ERROR, "DISPLAY: DRM: Swap buffers can not be initialized");
656 return -1;
657 }
658
659 // Render a blank frame to allocate buffers
660 RenderBlankFrame();
661
662 // Get first buffer
663 struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
664 if (!bo)
665 {
666 TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to lock initial swap buffer");
667 return -1;
668 }
669
670 // Create FB for first buffer
671 uint32_t fbId = GetOrCreateFbForBo(bo);
672 if (!fbId)
673 {
674 gbm_surface_release_buffer(platform.gbmSurface, bo);
675 return -1;
676 }
677
678 // Initial CRTC setup
679 if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]))
680 {
681 TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to initialize CRTC setup. ERROR: %s", strerror(errno));
682 gbm_surface_release_buffer(platform.gbmSurface, bo);
683 return -1;
684 }
685
686 // Keep first buffer locked until flipped
687 platform.prevBO = bo;
688 crtcSet = true;
689
690 return 0;
691}
692
693// Static page flip handler
694// NOTE: Called once the drmModePageFlip() finished from the drmHandleEvent() context
695static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
696{
697 // Unused inputs
698 (void)fd;
699 (void)frame;
700 (void)sec;
701 (void)usec;
702
703 pendingFlip = false;
704 struct gbm_bo *boToRelease = (struct gbm_bo *)data;
705
706 // Buffers are released after the flip completes (via page_flip_handler), ensuring they're no longer in use
707 // Prevents the GPU from writing to a buffer being scanned out
708 if (boToRelease) gbm_surface_release_buffer(platform.gbmSurface, boToRelease);
709}
710
711// Swap implementation with proper caching
712void SwapScreenBuffer()
713{
714 if (!crtcSet || !platform.gbmSurface) return;
715
716 static int loopCnt = 0;
717 static int errCnt[5] = { 0 };
718 loopCnt++;
719
720 // Call this only, if pendingFlip is not set
721 eglSwapBuffers(platform.device, platform.surface);
722
723 // Process pending events non-blocking
724 drmEventContext evctx = {
725 .version = DRM_EVENT_CONTEXT_VERSION,
726 .page_flip_handler = PageFlipHandler
727 };
728
729 struct pollfd pfd = { .fd = platform.fd, .events = POLLIN };
730
731 // Polling for event for 0ms
732 while (poll(&pfd, 1, 0) > 0) drmHandleEvent(platform.fd, &evctx);
733
734 // Skip if previous flip pending
735 if (pendingFlip)
736 {
737 errCnt[0]++; // Skip frame: flip pending
738 return;
739 }
740
741 // Get new front buffer
742 struct gbm_bo *nextBO = gbm_surface_lock_front_buffer(platform.gbmSurface);
743 if (!nextBO) // Failed to lock front buffer
744 {
745 errCnt[1]++;
746 return;
747 }
748
749 // Get FB ID (creates new one if needed)
750 uint32_t fbId = GetOrCreateFbForBo(nextBO);
751 if (!fbId)
752 {
753 gbm_surface_release_buffer(platform.gbmSurface, nextBO);
754 errCnt[2]++;
755 return;
756 }
757
758 // Attempt page flip
759 // NOTE: rmModePageFlip() schedules a buffer-flip for the next vblank and then notifies us about it
760 // It takes a CRTC-id, fb-id and an arbitrary data-pointer and then schedules the page-flip
761 // This is fully asynchronous and when the page-flip happens, the DRM-fd will become readable and we can call drmHandleEvent()
762 // This will read all vblank/page-flip events and call our modeset_page_flip_event() callback with the data-pointer that we passed to drmModePageFlip()
763 // We simply call modeset_draw_dev() then so the next frame is rendered... returns immediately
764 if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId, DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO))
765 {
766 if (errno == EBUSY) errCnt[3]++; // Display busy - skip flip
767 else errCnt[4]++; // Page flip failed
768
769 gbm_surface_release_buffer(platform.gbmSurface, nextBO);
770 return;
771 }
772
773 // Success: update state
774 pendingFlip = true;
775 platform.prevBO = nextBO;
776
777/*
778 // Some benchmarking code
779 if (loopCnt >= 600)
780 {
781 TRACELOG(LOG_INFO, "DRM: Error counters: %d, %d, %d, %d, %d, %d", errCnt[0], errCnt[1], errCnt[2], errCnt[3], errCnt[4], loopCnt);
782 for (int i = 0; i < 5; i++) errCnt[i] = 0;
783 loopCnt = 0;
784 }
785*/
786}
787
788#else // !SUPPORT_DRM_CACHE
789
790// Swap back buffer with front buffer (screen drawing)
791void SwapScreenBuffer(void)
792{
793#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
794 // Hardware rendering buffer swap with EGL
795 eglSwapBuffers(platform.device, platform.surface);
796
797 if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
798
799 struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
800 if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer");
801
802 uint32_t fb = 0;
803 int result = drmModeAddFB(platform.fd, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb);
804 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result);
805
806 result = drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]);
807 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result);
808
809 if (platform.prevFB)
810 {
811 result = drmModeRmFB(platform.fd, platform.prevFB);
812 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result);
813 }
814
815 platform.prevFB = fb;
816
817 if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
818
819 platform.prevBO = bo;
820#else
821 // Software rendering buffer swap
822 if ((platform.fd == -1) || !platform.connector || (platform.modeIndex < 0))
823 {
824 TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
825 return;
826 }
827
828 // Retrieving the dimensions of the display mode used
829 drmModeModeInfo *mode = &platform.connector->modes[platform.modeIndex];
830 uint32_t width = mode->hdisplay;
831 uint32_t height = mode->vdisplay;
832
833 // Dumb buffers use a fixed format based on bpp
834#if SW_COLOR_BUFFER_BITS == 24
835 const uint32_t bpp = 32; // 32 bits per pixel (XRGB8888 format)
836 const uint32_t depth = 24; // Color depth, here only 24 bits, alpha is not used
837#else
838 // REVIEW: Not sure how it will be interpreted (RGB or RGBA?)
839 const uint32_t bpp = SW_COLOR_BUFFER_BITS;
840 const uint32_t depth = SW_COLOR_BUFFER_BITS;
841#endif
842
843 // Create a dumb buffer for software rendering
844 struct drm_mode_create_dumb creq = { 0 };
845 creq.width = width;
846 creq.height = height;
847 creq.bpp = bpp;
848
849 int result = drmIoctl(platform.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
850 if (result < 0)
851 {
852 TRACELOG(LOG_ERROR, "DISPLAY: Failed to create dumb buffer: %s", strerror(errno));
853 return;
854 }
855
856 // Create framebuffer with the correct format
857 uint32_t fb = 0;
858 result = drmModeAddFB(platform.fd, width, height, depth, bpp, creq.pitch, creq.handle, &fb);
859 if (result != 0)
860 {
861 TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d (%s)", result, strerror(errno));
862 struct drm_mode_destroy_dumb dreq = { 0 };
863 dreq.handle = creq.handle;
864 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
865 return;
866 }
867
868 // Map the dumb buffer to copy our software rendered buffer
869 struct drm_mode_map_dumb mreq = { 0 };
870 mreq.handle = creq.handle;
871 result = drmIoctl(platform.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
872 if (result != 0)
873 {
874 TRACELOG(LOG_ERROR, "DISPLAY: Failed to map dumb buffer: %s", strerror(errno));
875 drmModeRmFB(platform.fd, fb);
876 struct drm_mode_destroy_dumb dreq = { 0 };
877 dreq.handle = creq.handle;
878 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
879 return;
880 }
881
882 // Map the buffer into userspace
883 void *dumbBuffer = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, platform.fd, mreq.offset);
884 if (dumbBuffer == MAP_FAILED)
885 {
886 TRACELOG(LOG_ERROR, "DISPLAY: Failed to mmap dumb buffer: %s", strerror(errno));
887 drmModeRmFB(platform.fd, fb);
888 struct drm_mode_destroy_dumb dreq = { 0 };
889 dreq.handle = creq.handle;
890 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
891 return;
892 }
893
894 // Copy the software rendered buffer to the dumb buffer with scaling if needed
895 // NOTE: RLSW will make a simple copy if the dimensions match
896 swBlitFramebuffer(0, 0, width, height, 0, 0, width, height, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer);
897
898 // Unmap the buffer
899 munmap(dumbBuffer, creq.size);
900
901 // Find a CRTC compatible with the connector
902 uint32_t crtcId = 0;
903 if (platform.crtc) crtcId = platform.crtc->crtc_id;
904 else
905 {
906 // Find a CRTC that's compatible with this connector
907 drmModeRes *res = drmModeGetResources(platform.fd);
908 if (!res)
909 {
910 TRACELOG(LOG_ERROR, "DISPLAY: Failed to get DRM resources");
911 drmModeRmFB(platform.fd, fb);
912 struct drm_mode_destroy_dumb dreq = {0};
913 dreq.handle = creq.handle;
914 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
915 return;
916 }
917
918 // Check which CRTCs are compatible with this connector
919 drmModeEncoder *encoder = NULL;
920 if (platform.connector->encoder_id) encoder = drmModeGetEncoder(platform.fd, platform.connector->encoder_id);
921
922 if (encoder && encoder->crtc_id)
923 {
924 crtcId = encoder->crtc_id;
925 platform.crtc = drmModeGetCrtc(platform.fd, crtcId);
926 }
927 else
928 {
929 // Find a free CRTC
930 for (int i = 0; i < res->count_crtcs; i++)
931 {
932 drmModeCrtc *crtc = drmModeGetCrtc(platform.fd, res->crtcs[i]);
933 if (crtc && !crtc->buffer_id) // CRTC is free
934 {
935 crtcId = res->crtcs[i];
936 if (platform.crtc) drmModeFreeCrtc(platform.crtc);
937 platform.crtc = crtc;
938 break;
939 }
940
941 if (crtc) drmModeFreeCrtc(crtc);
942 }
943 }
944
945 if (encoder) drmModeFreeEncoder(encoder);
946 drmModeFreeResources(res);
947
948 if (!crtcId)
949 {
950 TRACELOG(LOG_ERROR, "DISPLAY: No compatible CRTC found");
951 drmModeRmFB(platform.fd, fb);
952 struct drm_mode_destroy_dumb dreq = {0};
953 dreq.handle = creq.handle;
954 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
955 return;
956 }
957 }
958
959 // Set CRTC with better error handling
960 result = drmModeSetCrtc(platform.fd, crtcId, fb, 0, 0, &platform.connector->connector_id, 1, mode);
961 if (result != 0)
962 {
963 TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d (%s)", result, strerror(errno));
964 TRACELOG(LOG_ERROR, "DISPLAY: CRTC ID: %u, FB ID: %u, Connector ID: %u", crtcId, fb, platform.connector->connector_id);
965 TRACELOG(LOG_ERROR, "DISPLAY: Mode: %dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh);
966
967 drmModeRmFB(platform.fd, fb);
968 struct drm_mode_destroy_dumb dreq = {0};
969 dreq.handle = creq.handle;
970 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
971 return;
972 }
973
974 // Clean up previous framebuffer
975 if (platform.prevFB)
976 {
977 result = drmModeRmFB(platform.fd, platform.prevFB);
978 if (result != 0) TRACELOG(LOG_WARNING, "DISPLAY: drmModeRmFB() failed with result: %d", result);
979 }
980
981 platform.prevFB = fb;
982
983 // Clean up previous dumb buffer
984 if (platform.prevDumbHandle)
985 {
986 struct drm_mode_destroy_dumb dreq = {0};
987 dreq.handle = platform.prevDumbHandle;
988 drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
989 }
990
991 platform.prevDumbHandle = creq.handle;
992#endif
993}
994#endif // SUPPORT_DRM_CACHE
995
996//----------------------------------------------------------------------------------
997// Module Functions Definition: Misc
998//----------------------------------------------------------------------------------
999
1000// Get elapsed time measure in seconds since InitTimer()
1001double GetTime(void)
1002{
1003 double time = 0.0;
1004 struct timespec ts = { 0 };
1005 clock_gettime(CLOCK_MONOTONIC, &ts);
1006 unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
1007
1008 time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer()
1009
1010 return time;
1011}
1012
1013// Open URL with default system browser (if available)
1014// NOTE: This function is only safe to use if you control the URL given
1015// A user could craft a malicious string performing another action
1016// Only call this function yourself not with user input or make sure to check the string yourself
1017// REF: https://github.com/raysan5/raylib/issues/686
1018void OpenURL(const char *url)
1019{
1020 TRACELOG(LOG_WARNING, "OpenURL() not implemented on target platform");
1021}
1022
1023//----------------------------------------------------------------------------------
1024// Module Functions Definition: Inputs
1025//----------------------------------------------------------------------------------
1026
1027// Set internal gamepad mappings
1028int SetGamepadMappings(const char *mappings)
1029{
1030 TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform");
1031 return 0;
1032}
1033
1034// Set gamepad vibration
1035void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
1036{
1037 TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform");
1038}
1039
1040// Set mouse position XY
1041void SetMousePosition(int x, int y)
1042{
1043 CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
1044 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
1045}
1046
1047// Set mouse cursor
1048void SetMouseCursor(int cursor)
1049{
1050 TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform");
1051}
1052
1053// Get physical key name
1054const char *GetKeyName(int key)
1055{
1056 TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform");
1057 return "";
1058}
1059
1060// Register all input events
1061void PollInputEvents(void)
1062{
1063#if defined(SUPPORT_GESTURES_SYSTEM)
1064 // NOTE: Gestures update must be called every frame to reset gestures correctly
1065 // because ProcessGestureEvent() is just called on an event, not every frame
1066 UpdateGestures();
1067#endif
1068
1069 // Reset keys/chars pressed registered
1070 CORE.Input.Keyboard.keyPressedQueueCount = 0;
1071 CORE.Input.Keyboard.charPressedQueueCount = 0;
1072
1073 // Reset last gamepad button/axis registered state
1074 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
1075 //CORE.Input.Gamepad.axisCount = 0;
1076
1077 // Register previous keys states
1078 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
1079 {
1080 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
1081 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
1082 }
1083
1084 PollKeyboardEvents();
1085
1086#if defined(SUPPORT_SSH_KEYBOARD_RPI)
1087 // NOTE: Keyboard reading could be done using input_event(s) or just read from stdin, both methods are used here
1088 // stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console
1089 if (!platform.eventKeyboardMode) ProcessKeyboard();
1090#endif
1091
1092 // Check exit key
1093 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
1094
1095 // Register previous mouse position
1096 if (platform.cursorRelative) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f };
1097 else CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
1098
1099 // Register previous mouse states
1100 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
1101 CORE.Input.Mouse.currentWheelMove = platform.eventWheelMove;
1102 platform.eventWheelMove = (Vector2){ 0.0f, 0.0f };
1103
1104 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++)
1105 {
1106 CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
1107 CORE.Input.Mouse.currentButtonState[i] = platform.currentButtonStateEvdev[i];
1108 CORE.Input.Touch.currentTouchState[i] = platform.currentButtonStateEvdev[i];
1109 }
1110
1111 // Register gamepads buttons events
1112 PollGamepadEvents();
1113
1114 // Register previous touch states
1115 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
1116
1117 // Reset touch positions to invalid state
1118 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ -1, -1 };
1119
1120 // Map touch position to mouse position for convenience
1121 // NOTE: For DRM touchscreen devices, this mapping is disabled to avoid false touch detection
1122 // CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
1123
1124 // Handle the mouse/touch/gestures events:
1125 PollMouseEvents();
1126}
1127
1128//----------------------------------------------------------------------------------
1129// Module Internal Functions Definition
1130//----------------------------------------------------------------------------------
1131
1132// Initialize platform: graphics, inputs and more
1133int InitPlatform(void)
1134{
1135 platform.fd = -1;
1136 platform.connector = NULL;
1137 platform.modeIndex = -1;
1138 platform.crtc = NULL;
1139 platform.prevFB = 0;
1140
1141#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1142 platform.gbmDevice = NULL;
1143 platform.gbmSurface = NULL;
1144 platform.prevBO = NULL;
1145#else
1146 platform.prevDumbHandle = 0;
1147#endif
1148
1149 // Initialize graphic device: display/window and graphic context
1150 //----------------------------------------------------------------------------
1151 FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
1152
1153#if defined(DEFAULT_GRAPHIC_DEVICE_DRM)
1154 platform.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR);
1155 if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: Default graphic device DRM opened successfully");
1156#else
1157 TRACELOG(LOG_WARNING, "DISPLAY: No graphic card set, trying platform-gpu-card");
1158 platform.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4)
1159 if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: platform-gpu-card opened successfully");
1160
1161 if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
1162 {
1163 if (platform.fd != -1) close(platform.fd);
1164 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open platform-gpu-card, trying card1");
1165 platform.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded
1166 if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card1 opened successfully");
1167 }
1168
1169 if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
1170 {
1171 if (platform.fd != -1) close(platform.fd);
1172 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card1, trying card0");
1173 platform.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3)
1174 if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card0 opened successfully");
1175 }
1176
1177 if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
1178 {
1179 if (platform.fd != -1) close(platform.fd);
1180 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card0, trying card2");
1181 platform.fd = open("/dev/dri/card2", O_RDWR);
1182 if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card2 opened successfully");
1183 }
1184#endif
1185
1186 if (platform.fd == -1)
1187 {
1188 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card");
1189 return -1;
1190 }
1191
1192 drmModeRes *res = drmModeGetResources(platform.fd);
1193 if (!res)
1194 {
1195 TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources");
1196 close(platform.fd);
1197 return -1;
1198 }
1199
1200 TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors);
1201
1202 // Connector detection
1203 for (size_t i = 0; i < res->count_connectors; i++)
1204 {
1205 TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i);
1206
1207 drmModeConnector *con = drmModeGetConnector(platform.fd, res->connectors[i]);
1208 if (!con)
1209 {
1210 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get connector %i", i);
1211 continue;
1212 }
1213
1214 TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes);
1215 TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i,
1216 (con->connection == DRM_MODE_CONNECTED)? "CONNECTED" :
1217 (con->connection == DRM_MODE_DISCONNECTED)? "DISCONNECTED" :
1218 (con->connection == DRM_MODE_UNKNOWNCONNECTION)? "UNKNOWN" : "OTHER");
1219
1220 // In certain cases the status of the conneciton is reported as UKNOWN, but it is still connected
1221 // This might be a hardware or software limitation like on Raspberry Pi Zero with composite output
1222 // WARNING: Accept CONNECTED, UNKNOWN and even those without encoder_id connectors for software mode
1223 if (((con->connection == DRM_MODE_CONNECTED) || (con->connection == DRM_MODE_UNKNOWNCONNECTION)) && (con->count_modes > 0))
1224 {
1225#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1226 // For hardware rendering, we need an encoder_id
1227 if (con->encoder_id)
1228 {
1229 TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected with encoder", i);
1230 platform.connector = con;
1231 break;
1232 }
1233 else TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected but no encoder", i);
1234#else
1235 // For software rendering, we can accept even without encoder_id
1236 TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i suitable for software rendering", i);
1237 platform.connector = con;
1238 break;
1239#endif
1240 }
1241
1242 if (!platform.connector)
1243 {
1244 TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i NOT suitable (deleting)", i);
1245 drmModeFreeConnector(con);
1246 }
1247 }
1248
1249 if (!platform.connector)
1250 {
1251 TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found");
1252 drmModeFreeResources(res);
1253 close(platform.fd);
1254 return -1;
1255 }
1256
1257#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1258 drmModeEncoder *enc = drmModeGetEncoder(platform.fd, platform.connector->encoder_id);
1259 if (!enc)
1260 {
1261 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder");
1262 drmModeFreeConnector(platform.connector);
1263 drmModeFreeResources(res);
1264 close(platform.fd);
1265 return -1;
1266 }
1267
1268 platform.crtc = drmModeGetCrtc(platform.fd, enc->crtc_id);
1269 if (!platform.crtc)
1270 {
1271 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc");
1272 drmModeFreeEncoder(enc);
1273 drmModeFreeConnector(platform.connector);
1274 drmModeFreeResources(res);
1275 close(platform.fd);
1276 return -1;
1277 }
1278
1279 // If InitWindow should use the current mode find it in the connector's mode list
1280 if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0))
1281 {
1282 TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode...");
1283
1284 platform.modeIndex = FindMatchingConnectorMode(platform.connector, &platform.crtc->mode);
1285
1286 if (platform.modeIndex < 0)
1287 {
1288 TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found");
1289 drmModeFreeEncoder(enc);
1290 drmModeFreeConnector(platform.connector);
1291 drmModeFreeResources(res);
1292 close(platform.fd);
1293 return -1;
1294 }
1295
1296 CORE.Window.screen.width = CORE.Window.display.width;
1297 CORE.Window.screen.height = CORE.Window.display.height;
1298 }
1299
1300 const bool allowInterlaced = FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT);
1301 const int fps = (CORE.Time.target > 0)? (1.0/CORE.Time.target) : 60;
1302
1303 // Try to find an exact matching mode
1304 platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
1305
1306 // If nothing found, try to find a nearly matching mode
1307 if (platform.modeIndex < 0) platform.modeIndex = FindNearestConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
1308
1309 // If nothing found, try to find an exactly matching mode including interlaced
1310 if (platform.modeIndex < 0) platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
1311
1312 // If nothing found, try to find a nearly matching mode including interlaced
1313 if (platform.modeIndex < 0) platform.modeIndex = FindNearestConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
1314
1315 // If nothing found, there is no suitable mode
1316 if (platform.modeIndex < 0)
1317 {
1318 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode");
1319 drmModeFreeEncoder(enc);
1320 drmModeFreeConnector(platform.connector);
1321 drmModeFreeResources(res);
1322 close(platform.fd);
1323 return -1;
1324 }
1325
1326 CORE.Window.display.width = platform.connector->modes[platform.modeIndex].hdisplay;
1327 CORE.Window.display.height = platform.connector->modes[platform.modeIndex].vdisplay;
1328
1329 TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", platform.connector->modes[platform.modeIndex].name,
1330 platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay,
1331 FLAG_IS_SET(platform.connector->modes[platform.modeIndex].flags, DRM_MODE_FLAG_INTERLACE)? 'i' : 'p',
1332 platform.connector->modes[platform.modeIndex].vrefresh);
1333
1334 drmModeFreeEncoder(enc);
1335 enc = NULL;
1336#else
1337 // For software rendering, the first available mode can be used
1338 if (platform.connector->count_modes > 0)
1339 {
1340 platform.modeIndex = 0;
1341 CORE.Window.display.width = platform.connector->modes[0].hdisplay;
1342 CORE.Window.display.height = platform.connector->modes[0].vdisplay;
1343
1344 TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering",
1345 platform.connector->modes[0].name,
1346 platform.connector->modes[0].hdisplay,
1347 platform.connector->modes[0].vdisplay,
1348 (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE)? 'i' : 'p',
1349 platform.connector->modes[0].vrefresh);
1350 }
1351 else
1352 {
1353 TRACELOG(LOG_WARNING, "DISPLAY: No modes available for connector");
1354 drmModeFreeConnector(platform.connector);
1355 drmModeFreeResources(res);
1356 close(platform.fd);
1357 return -1;
1358 }
1359#endif
1360
1361 // Use the width and height of the surface for render
1362 CORE.Window.render.width = CORE.Window.screen.width;
1363 CORE.Window.render.height = CORE.Window.screen.height;
1364
1365 drmModeFreeResources(res);
1366 res = NULL;
1367
1368#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1369 // Hardware rendering initialization with EGL
1370 platform.gbmDevice = gbm_create_device(platform.fd);
1371 if (!platform.gbmDevice)
1372 {
1373 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device");
1374 return -1;
1375 }
1376
1377 platform.gbmSurface = gbm_surface_create(platform.gbmDevice, platform.connector->modes[platform.modeIndex].hdisplay,
1378 platform.connector->modes[platform.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
1379 if (!platform.gbmSurface)
1380 {
1381 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface");
1382 return -1;
1383 }
1384
1385 EGLint samples = 0;
1386 EGLint sampleBuffer = 0;
1387 if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT))
1388 {
1389 samples = 4;
1390 sampleBuffer = 1;
1391 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
1392 }
1393
1394 const EGLint framebufferAttribs[] = {
1395 EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support
1396 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android!
1397 EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5)
1398 EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6)
1399 EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5)
1400 EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer)
1401 //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI)
1402 EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!)
1403 //EGL_STENCIL_SIZE, 8, // Stencil buffer size
1404 EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA
1405 EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs)
1406 EGL_NONE
1407 };
1408
1409 const EGLint contextAttribs[] = {
1410 EGL_CONTEXT_CLIENT_VERSION, 2,
1411 EGL_NONE
1412 };
1413
1414 EGLint numConfigs = 0;
1415
1416 // Get an EGL device connection
1417 platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice);
1418 if (platform.device == EGL_NO_DISPLAY)
1419 {
1420 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
1421 return -1;
1422 }
1423
1424 // Initialize the EGL device connection
1425 if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE)
1426 {
1427 // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred
1428 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
1429 return -1;
1430 }
1431
1432 if (!eglChooseConfig(platform.device, NULL, NULL, 0, &numConfigs))
1433 {
1434 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError());
1435 return -1;
1436 }
1437
1438 TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs);
1439
1440 EGLConfig *configs = (EGLConfig *)RL_CALLOC(numConfigs, sizeof(*configs));
1441 if (!configs)
1442 {
1443 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs");
1444 return -1;
1445 }
1446
1447 EGLint matchingNumConfigs = 0;
1448 if (!eglChooseConfig(platform.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs))
1449 {
1450 TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError());
1451 free(configs);
1452 return -1;
1453 }
1454
1455 TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs);
1456
1457 // find the EGL config that matches the previously setup GBM format
1458 int found = 0;
1459 for (EGLint i = 0; i < matchingNumConfigs; i++)
1460 {
1461 EGLint id = 0;
1462 if (!eglGetConfigAttrib(platform.device, configs[i], EGL_NATIVE_VISUAL_ID, &id))
1463 {
1464 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError());
1465 continue;
1466 }
1467
1468 if (GBM_FORMAT_ARGB8888 == id)
1469 {
1470 TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i);
1471 platform.config = configs[i];
1472 found = 1;
1473 break;
1474 }
1475 }
1476
1477 RL_FREE(configs);
1478
1479 if (!found)
1480 {
1481 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config");
1482 return -1;
1483 }
1484
1485 // Set rendering API
1486 eglBindAPI(EGL_OPENGL_ES_API);
1487
1488 // Create an EGL rendering context
1489 platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs);
1490 if (platform.context == EGL_NO_CONTEXT)
1491 {
1492 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context");
1493 return -1;
1494 }
1495
1496 // Create an EGL window surface
1497 platform.surface = eglCreateWindowSurface(platform.device, platform.config, (EGLNativeWindowType)platform.gbmSurface, NULL);
1498 if (EGL_NO_SURFACE == platform.surface)
1499 {
1500 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError());
1501 return -1;
1502 }
1503
1504 // At this point we need to manage render size vs screen size
1505 // NOTE: This function use and modify global module variables:
1506 // -> CORE.Window.screen.width/CORE.Window.screen.height
1507 // -> CORE.Window.render.width/CORE.Window.render.height
1508 // -> CORE.Window.screenScale
1509 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
1510
1511 // There must be at least one frame displayed before the buffers are swapped
1512 //eglSwapInterval(platform.device, 1);
1513
1514 EGLBoolean result = eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context);
1515
1516 // Check surface and context activation
1517 if (result != EGL_FALSE)
1518 {
1519 CORE.Window.ready = true;
1520
1521 CORE.Window.render.width = CORE.Window.screen.width;
1522 CORE.Window.render.height = CORE.Window.screen.height;
1523 CORE.Window.currentFbo.width = CORE.Window.render.width;
1524 CORE.Window.currentFbo.height = CORE.Window.render.height;
1525
1526 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
1527 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
1528 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
1529 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
1530 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
1531 }
1532 else
1533 {
1534 TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device");
1535 return -1;
1536 }
1537
1538 // Load OpenGL extensions
1539 // NOTE: GL procedures address loader is required to load extensions
1540 rlLoadExtensions(eglGetProcAddress);
1541#else
1542 // At this point we need to manage render size vs screen size
1543 // NOTE: This function use and modify global module variables:
1544 // -> CORE.Window.screen.width/CORE.Window.screen.height
1545 // -> CORE.Window.render.width/CORE.Window.render.height
1546 // -> CORE.Window.screenScale
1547 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
1548
1549 // Setup window ready state for software rendering
1550 CORE.Window.ready = true;
1551
1552 CORE.Window.render.width = CORE.Window.screen.width;
1553 CORE.Window.render.height = CORE.Window.screen.height;
1554 CORE.Window.currentFbo.width = CORE.Window.render.width;
1555 CORE.Window.currentFbo.height = CORE.Window.render.height;
1556
1557 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully (Software Rendering)");
1558 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
1559 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
1560 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
1561 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
1562#endif
1563
1564 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow();
1565
1566 // If graphic device is no properly initialized, we end program
1567 if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; }
1568 else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2);
1569
1570 // Set some default window flags
1571 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // false
1572 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // false
1573 FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // true
1574 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // false
1575
1576 //----------------------------------------------------------------------------
1577 // Initialize timing system
1578 //----------------------------------------------------------------------------
1579 // NOTE: timming system must be initialized before the input events system
1580 InitTimer();
1581 //----------------------------------------------------------------------------
1582
1583 // Initialize input events system
1584 //----------------------------------------------------------------------------
1585 InitEvdevInput(); // Evdev inputs initialization
1586
1587#if defined(SUPPORT_SSH_KEYBOARD_RPI)
1588 InitKeyboard(); // Keyboard init (stdin)
1589#endif
1590 //----------------------------------------------------------------------------
1591
1592 // Initialize storage system
1593 //----------------------------------------------------------------------------
1594 CORE.Storage.basePath = GetWorkingDirectory();
1595 //----------------------------------------------------------------------------
1596
1597#if defined(SUPPORT_DRM_CACHE)
1598 if (InitSwapScreenBuffer() == 0)
1599 {
1600 TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
1601 return 0;
1602 }
1603 else
1604 {
1605 TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed");
1606 return -1;
1607 }
1608#else // !SUPPORT_DRM_CACHE
1609 TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
1610 return 0;
1611#endif
1612}
1613
1614// Close platform
1615void ClosePlatform(void)
1616{
1617 if (platform.prevFB)
1618 {
1619 drmModeRmFB(platform.fd, platform.prevFB);
1620 platform.prevFB = 0;
1621 }
1622
1623#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1624 if (platform.prevBO)
1625 {
1626 gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
1627 platform.prevBO = NULL;
1628 }
1629
1630 if (platform.gbmSurface)
1631 {
1632 gbm_surface_destroy(platform.gbmSurface);
1633 platform.gbmSurface = NULL;
1634 }
1635
1636 if (platform.gbmDevice)
1637 {
1638 gbm_device_destroy(platform.gbmDevice);
1639 platform.gbmDevice = NULL;
1640 }
1641#endif
1642
1643 if (platform.crtc)
1644 {
1645 if (platform.connector)
1646 {
1647 drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, platform.crtc->buffer_id,
1648 platform.crtc->x, platform.crtc->y, &platform.connector->connector_id, 1, &platform.crtc->mode);
1649 drmModeFreeConnector(platform.connector);
1650 platform.connector = NULL;
1651 }
1652
1653 drmModeFreeCrtc(platform.crtc);
1654 platform.crtc = NULL;
1655 }
1656
1657 if (platform.fd != -1)
1658 {
1659 close(platform.fd);
1660 platform.fd = -1;
1661 }
1662
1663#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1664 // Close surface, context and display
1665 if (platform.device != EGL_NO_DISPLAY)
1666 {
1667 if (platform.surface != EGL_NO_SURFACE)
1668 {
1669 eglDestroySurface(platform.device, platform.surface);
1670 platform.surface = EGL_NO_SURFACE;
1671 }
1672
1673 if (platform.context != EGL_NO_CONTEXT)
1674 {
1675 eglDestroyContext(platform.device, platform.context);
1676 platform.context = EGL_NO_CONTEXT;
1677 }
1678
1679 eglTerminate(platform.device);
1680 platform.device = EGL_NO_DISPLAY;
1681 }
1682#endif
1683
1684 CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called
1685
1686 // Close the evdev devices
1687
1688 if (platform.mouseFd != -1)
1689 {
1690 close(platform.mouseFd);
1691 platform.mouseFd = -1;
1692 }
1693
1694 for (int i = 0; i < platform.gamepadCount; i++)
1695 {
1696 close(platform.gamepadStreamFd[i]);
1697 platform.gamepadStreamFd[i] = -1;
1698 }
1699
1700 if (platform.keyboardFd != -1)
1701 {
1702 close(platform.keyboardFd);
1703 platform.keyboardFd = -1;
1704 }
1705}
1706
1707#if defined(SUPPORT_SSH_KEYBOARD_RPI)
1708// Initialize Keyboard system (using standard input)
1709static void InitKeyboard(void)
1710{
1711 // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor,
1712 // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE
1713
1714 // Save terminal keyboard settings
1715 tcgetattr(STDIN_FILENO, &platform.defaultSettings);
1716
1717 // Reconfigure terminal with new settings
1718 struct termios keyboardNewSettings = { 0 };
1719 keyboardNewSettings = platform.defaultSettings;
1720
1721 // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing
1722 // NOTE: ISIG controls if ^C and ^Z generate break signals or not
1723 FLAG_CLEAR(keyboardNewSettings.c_lflag, ICANON | ECHO | ISIG);
1724 //FLAG_CLEAR(keyboardNewSettings.c_iflag, ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF);
1725 keyboardNewSettings.c_cc[VMIN] = 1;
1726 keyboardNewSettings.c_cc[VTIME] = 0;
1727
1728 // Set new keyboard settings (change occurs immediately)
1729 tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings);
1730
1731 // Save old keyboard mode to restore it at the end
1732 platform.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags
1733 fcntl(STDIN_FILENO, F_SETFL, platform.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified
1734
1735 // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno)
1736 int result = ioctl(STDIN_FILENO, KDGKBMODE, &platform.defaultKeyboardMode);
1737
1738 // In case of failure, it could mean a remote keyboard is used (SSH)
1739 if (result < 0) TRACELOG(LOG_WARNING, "DRM: Failed to change keyboard mode, an SSH keyboard is probably used");
1740 else
1741 {
1742 // Reconfigure keyboard mode to get:
1743 // - scancodes (K_RAW)
1744 // - keycodes (K_MEDIUMRAW)
1745 // - ASCII chars (K_XLATE)
1746 // - UNICODE chars (K_UNICODE)
1747 ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars
1748 }
1749
1750 // Register keyboard restore when program finishes
1751 atexit(RestoreKeyboard);
1752}
1753
1754// Restore default keyboard input
1755static void RestoreKeyboard(void)
1756{
1757 // Reset to default keyboard settings
1758 tcsetattr(STDIN_FILENO, TCSANOW, &platform.defaultSettings);
1759
1760 // Reconfigure keyboard to default mode
1761 fcntl(STDIN_FILENO, F_SETFL, platform.defaultFileFlags);
1762 ioctl(STDIN_FILENO, KDSKBMODE, platform.defaultKeyboardMode);
1763}
1764
1765// Process keyboard inputs
1766static void ProcessKeyboard(void)
1767{
1768 #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read
1769
1770 // Keyboard input polling (fill keys[256] array with status)
1771 int bufferByteCount = 0; // Bytes available on the buffer
1772 char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time
1773
1774 // Read availables keycodes from stdin
1775 bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call
1776
1777 // Reset pressed keys array (it will be filled below)
1778 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
1779 {
1780 CORE.Input.Keyboard.currentKeyState[i] = 0;
1781 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
1782 }
1783
1784 // Fill all read bytes (looking for keys)
1785 for (int i = 0; i < bufferByteCount; i++)
1786 {
1787 // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code!
1788 // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42
1789 if (keysBuffer[i] == 0x1b)
1790 {
1791 // Check if ESCAPE key has been pressed to stop program
1792 if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1;
1793 else
1794 {
1795 if (keysBuffer[i + 1] == 0x5b) // Special function key
1796 {
1797 if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32))
1798 {
1799 // Process special function keys (F1 - F12)
1800 switch (keysBuffer[i + 3])
1801 {
1802 case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1
1803 case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2
1804 case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3
1805 case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4
1806 case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5
1807 case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6
1808 case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7
1809 case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8
1810 case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9
1811 case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10
1812 case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11
1813 case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12
1814 default: break;
1815 }
1816
1817 if (keysBuffer[i + 2] == 0x5b) i += 4;
1818 else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5;
1819 }
1820 else
1821 {
1822 switch (keysBuffer[i + 2])
1823 {
1824 case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP
1825 case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN
1826 case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT
1827 case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT
1828 default: break;
1829 }
1830
1831 i += 3; // Jump to next key
1832 }
1833
1834 // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT)
1835 }
1836 }
1837 }
1838 else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*)
1839 {
1840 CORE.Input.Keyboard.currentKeyState[257] = 1;
1841
1842 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue
1843 CORE.Input.Keyboard.keyPressedQueueCount++;
1844 }
1845 else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE
1846 {
1847 CORE.Input.Keyboard.currentKeyState[259] = 1;
1848
1849 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 259; // Add keys pressed into queue
1850 CORE.Input.Keyboard.keyPressedQueueCount++;
1851 }
1852 else
1853 {
1854 // Translate lowercase a-z letters to A-Z
1855 if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122))
1856 {
1857 CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1;
1858 }
1859 else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1;
1860
1861 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue
1862 CORE.Input.Keyboard.keyPressedQueueCount++;
1863 }
1864 }
1865}
1866#endif // SUPPORT_SSH_KEYBOARD_RPI
1867
1868// Initialize user input from evdev(/dev/input/event<N>)
1869// this means mouse, keyboard or gamepad devices
1870static void InitEvdevInput(void)
1871{
1872 char path[MAX_FILEPATH_LENGTH] = { 0 };
1873 DIR *directory = NULL;
1874 struct dirent *entity = NULL;
1875
1876 // Initialize keyboard file descriptor
1877 platform.keyboardFd = -1;
1878 platform.mouseFd = -1;
1879
1880 // Reset variables
1881 for (int i = 0; i < MAX_TOUCH_POINTS; i++)
1882 {
1883 CORE.Input.Touch.position[i].x = -1;
1884 CORE.Input.Touch.position[i].y = -1;
1885 }
1886
1887 // Reset keyboard key state
1888 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
1889 {
1890 CORE.Input.Keyboard.currentKeyState[i] = 0;
1891 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
1892 }
1893
1894 // Open the linux directory of "/dev/input"
1895 directory = opendir(DEFAULT_EVDEV_PATH);
1896
1897 if (directory)
1898 {
1899 while ((entity = readdir(directory)) != NULL)
1900 {
1901 if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*"
1902 (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*"
1903 {
1904 snprintf(path, MAX_FILEPATH_LENGTH, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name);
1905 ConfigureEvdevDevice(path); // Configure the device if appropriate
1906 }
1907 }
1908
1909 closedir(directory);
1910 }
1911 else TRACELOG(LOG_WARNING, "INPUT: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH);
1912}
1913
1914// Identifies a input device and configures it for use if appropriate
1915static void ConfigureEvdevDevice(char *device)
1916{
1917 #define BITS_PER_LONG (8*sizeof(long))
1918 #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1)
1919 #define OFF(x) ((x)%BITS_PER_LONG)
1920 #define BIT(x) (1UL<<OFF(x))
1921 #define LONG(x) ((x)/BITS_PER_LONG)
1922 #define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1)
1923
1924 unsigned long evBits[NBITS(EV_MAX)] = { 0 };
1925 unsigned long absBits[NBITS(ABS_MAX)] = { 0 };
1926 unsigned long relBits[NBITS(REL_MAX)] = { 0 };
1927 unsigned long keyBits[NBITS(KEY_MAX)] = { 0 };
1928
1929 // Open the device
1930 int fd = open(device, O_RDONLY | O_NONBLOCK);
1931 if (fd < 0)
1932 {
1933 TRACELOG(LOG_WARNING, "SYSTEM: Failed to open input device: %s", device);
1934 return;
1935 }
1936
1937 // At this point we have a connection to the device, but we don't yet know what the device is
1938 // It could be many things, even as simple as a power button...
1939 //-------------------------------------------------------------------------------------------------------
1940
1941 // Identify the device
1942 //-------------------------------------------------------------------------------------------------------
1943 struct {
1944 bool exist;
1945 struct input_absinfo info;
1946 } absinfo[ABS_CNT] = { 0 };
1947
1948 // These flags aren't really a one of
1949 // Some devices could have properties we assosciate with keyboards as well as properties
1950 // we assosciate with mice
1951 bool isKeyboard = false;
1952 bool isMouse = false;
1953 bool isTouch = false;
1954 bool isGamepad = false;
1955
1956 int absAxisCount = 0;
1957
1958 ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits);
1959 ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits);
1960
1961 if (TEST_BIT(evBits, EV_ABS))
1962 {
1963 ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits);
1964
1965 // If the device has an X an Y axis it's either a touch device, a special mouse or a gamepad
1966 bool hasAbsXY = TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y);
1967
1968 if (hasAbsXY)
1969 {
1970 absAxisCount += 2;
1971
1972 absinfo[ABS_X].exist = true;
1973 absinfo[ABS_Y].exist = true;
1974
1975 ioctl(fd, EVIOCGABS(ABS_X), &absinfo[ABS_X].info);
1976 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo[ABS_Y].info);
1977 }
1978
1979 // If it has any of these buttons it's a touch device
1980 if (hasAbsXY &&
1981 (TEST_BIT(keyBits, BTN_STYLUS) ||
1982 TEST_BIT(keyBits, BTN_TOOL_PEN) ||
1983 TEST_BIT(keyBits, BTN_TOOL_FINGER) ||
1984 TEST_BIT(keyBits, BTN_TOUCH))) isTouch = true;
1985
1986 // Absolute mice should really only exist with VMWare, but it shouldn't
1987 // matter if we support them
1988 else if (hasAbsXY && TEST_BIT(keyBits, BTN_MOUSE)) isMouse = true;
1989
1990 // If any of the common joystick axes are present, we assume it's a gamepad
1991 else
1992 {
1993 for (int axis = (hasAbsXY? ABS_Z : ABS_X); axis < ABS_PRESSURE; axis++)
1994 {
1995 if (TEST_BIT(absBits, axis))
1996 {
1997 absinfo[axis].exist = true;
1998 isGamepad = true;
1999 absAxisCount++;
2000
2001 ioctl(fd, EVIOCGABS(axis), &absinfo[axis].info);
2002 }
2003 }
2004 }
2005
2006 // If the device has multitouch axes, it's a touch device
2007 if (TEST_BIT(absBits, ABS_MT_POSITION_X) &&
2008 TEST_BIT(absBits, ABS_MT_POSITION_Y)) isTouch = true;
2009 }
2010
2011 if (TEST_BIT(evBits, EV_REL))
2012 {
2013 ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits);
2014
2015 // If it has any of the gamepad or touch features we tested so far, it's not a mouse
2016 if (!isTouch &&
2017 !isGamepad &&
2018 TEST_BIT(relBits, REL_X) &&
2019 TEST_BIT(relBits, REL_Y) &&
2020 TEST_BIT(keyBits, BTN_MOUSE)) isMouse = true;
2021 }
2022
2023 if (TEST_BIT(evBits, EV_KEY))
2024 {
2025 // The first 32 keys as defined in input-event-codes.h are pretty much
2026 // exclusive to keyboards, so we can test them using a mask
2027 // Leave out the first bit to not test KEY_RESERVED
2028 const unsigned long mask = 0xFFFFFFFE;
2029 if ((keyBits[0] & mask) == mask) isKeyboard = true;
2030
2031 // If we find any of the common gamepad buttons we assume it's a gamepad
2032 else
2033 {
2034 for (int button = BTN_JOYSTICK; button < BTN_DIGI; ++button)
2035 {
2036 if (TEST_BIT(keyBits, button)) isGamepad = true;
2037 }
2038
2039 for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40; button++)
2040 {
2041 if (TEST_BIT(keyBits, button)) isGamepad = true;
2042 }
2043 }
2044 }
2045
2046 const char *deviceKindStr = "unknown";
2047 if (isMouse || isTouch)
2048 {
2049 deviceKindStr = "mouse";
2050 if (platform.mouseFd != -1) close(platform.mouseFd);
2051 platform.mouseFd = fd;
2052
2053 if (absAxisCount > 0)
2054 {
2055 platform.absRange.x = absinfo[ABS_X].info.minimum;
2056 platform.absRange.width = absinfo[ABS_X].info.maximum - absinfo[ABS_X].info.minimum;
2057
2058 platform.absRange.y = absinfo[ABS_Y].info.minimum;
2059 platform.absRange.height = absinfo[ABS_Y].info.maximum - absinfo[ABS_Y].info.minimum;
2060 }
2061 }
2062 else if (isGamepad && !isMouse && !isKeyboard && (platform.gamepadCount < MAX_GAMEPADS))
2063 {
2064 deviceKindStr = "gamepad";
2065 int index = platform.gamepadCount++;
2066
2067 platform.gamepadStreamFd[index] = fd;
2068 CORE.Input.Gamepad.ready[index] = true;
2069
2070 ioctl(platform.gamepadStreamFd[index], EVIOCGNAME(64), &CORE.Input.Gamepad.name[index]);
2071 CORE.Input.Gamepad.axisCount[index] = absAxisCount;
2072
2073 if (absAxisCount > 0)
2074 {
2075 // TODO / NOTE
2076 // So gamepad axes (as in the actual linux joydev.c) are just simply enumerated
2077 // and (at least for some input drivers like xpat) it's convention to use
2078 // ABS_X, ABX_Y for one joystick ABS_RX, ABS_RY for the other and the Z axes for the
2079 // shoulder buttons
2080 // If these are now enumerated you get LJOY_X, LJOY_Y, LEFT_SHOULDERB, RJOY_X, ...
2081 // That means they don't match the GamepadAxis enum
2082 // This could be fixed
2083 int axisIndex = 0;
2084 for (int axis = ABS_X; axis < ABS_PRESSURE; axis++)
2085 {
2086 if (absinfo[axis].exist)
2087 {
2088 platform.gamepadAbsAxisRange[index][axisIndex][0] = absinfo[axisIndex].info.minimum;
2089 platform.gamepadAbsAxisRange[index][axisIndex][1] = absinfo[axisIndex].info.maximum - absinfo[axisIndex].info.minimum;
2090
2091 platform.gamepadAbsAxisMap[index][axis] = axisIndex;
2092 axisIndex++;
2093 }
2094 }
2095 }
2096 }
2097 else if (isKeyboard && (platform.keyboardFd == -1))
2098 {
2099 deviceKindStr = "keyboard";
2100 platform.keyboardFd = fd;
2101 }
2102 else
2103 {
2104 close(fd);
2105 return;
2106 }
2107
2108 TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr);
2109}
2110
2111// Poll and process evdev keyboard events
2112static void PollKeyboardEvents(void)
2113{
2114 int fd = platform.keyboardFd;
2115 if (fd == -1) return;
2116
2117 struct input_event event = { 0 };
2118 int keycode = -1;
2119
2120 // Try to read data from the keyboard and only continue if successful
2121 while (read(fd, &event, sizeof(event)) == (int)sizeof(event))
2122 {
2123 // Check if the event is a key event
2124 if (event.type != EV_KEY) continue;
2125
2126#if defined(SUPPORT_SSH_KEYBOARD_RPI)
2127 // If the event was a key, we know a working keyboard is connected, so disable the SSH keyboard
2128 platform.eventKeyboardMode = true;
2129#endif
2130
2131 // Keyboard keys appear for codes 1 to 255, ignore everthing else
2132 if ((event.code >= 1) && (event.code <= 255))
2133 {
2134
2135 // Lookup the scancode in the keymap to get a keycode
2136 keycode = linuxToRaylibMap[event.code];
2137
2138 // Make sure we got a valid keycode
2139 if ((keycode > 0) && (keycode < MAX_KEYBOARD_KEYS))
2140 {
2141
2142 // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt
2143 // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL,
2144 // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat
2145 CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1);
2146 CORE.Input.Keyboard.keyRepeatInFrame[keycode] = (event.value == 2);
2147
2148 // If the key is pressed add it to the queues
2149 if (event.value == 1)
2150 {
2151 if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
2152 {
2153 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
2154 CORE.Input.Keyboard.keyPressedQueueCount++;
2155 }
2156
2157 if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
2158 {
2159 // TODO/FIXME: This is not actually converting to unicode properly because it's not taking things like shift into account
2160 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = evkeyToUnicodeLUT[event.code];
2161 CORE.Input.Keyboard.charPressedQueueCount++;
2162 }
2163 }
2164
2165 TRACELOG(LOG_DEBUG, "INPUT: KEY_%s Keycode(linux): %4i KeyCode(raylib): %4i", (event.value == 0)? "UP " : "DOWN", event.code, keycode);
2166 }
2167 }
2168 }
2169}
2170
2171// Poll gamepad input events
2172static void PollGamepadEvents(void)
2173{
2174 // Read gamepad event
2175 struct input_event event = { 0 };
2176
2177 for (int i = 0; i < platform.gamepadCount; i++)
2178 {
2179 if (!CORE.Input.Gamepad.ready[i]) continue;
2180
2181 // Register previous gamepad states
2182 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
2183
2184 while (read(platform.gamepadStreamFd[i], &event, sizeof(event)) == (int)sizeof(event))
2185 {
2186 if (event.type == EV_KEY)
2187 {
2188 if (event.code < KEYMAP_SIZE)
2189 {
2190 short keycodeRaylib = linuxToRaylibMap[event.code];
2191
2192 TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: KEY_%s Keycode(linux): %4i Keycode(raylib): %4i", i, (event.value == 0)? "UP" : "DOWN", event.code, keycodeRaylib);
2193
2194 if ((keycodeRaylib != 0) && (keycodeRaylib < MAX_GAMEPAD_BUTTONS))
2195 {
2196 // 1 - button pressed, 0 - button released
2197 CORE.Input.Gamepad.currentButtonState[i][keycodeRaylib] = event.value;
2198
2199 CORE.Input.Gamepad.lastButtonPressed = (event.value == 1)? keycodeRaylib : GAMEPAD_BUTTON_UNKNOWN;
2200 }
2201 }
2202 }
2203 else if (event.type == EV_ABS)
2204 {
2205 if (event.code < ABS_CNT)
2206 {
2207 int axisRaylib = platform.gamepadAbsAxisMap[i][event.code];
2208
2209 TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: Axis: %2i Value: %i", i, axisRaylib, event.value);
2210
2211 if (axisRaylib < MAX_GAMEPAD_AXES)
2212 {
2213 int min = platform.gamepadAbsAxisRange[i][event.code][0];
2214 int range = platform.gamepadAbsAxisRange[i][event.code][1];
2215
2216 // NOTE: Scaling of event.value to get values between -1..1
2217 CORE.Input.Gamepad.axisState[i][axisRaylib] = (2*(float)(event.value - min)/range) - 1;
2218 }
2219 }
2220 }
2221 }
2222 }
2223}
2224
2225// Poll mouse input events
2226static void PollMouseEvents(void)
2227{
2228 int fd = platform.mouseFd;
2229 if (fd == -1) return;
2230
2231 struct input_event event = { 0 };
2232 int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE
2233
2234 // Try to read data from the mouse/touch/gesture and only continue if successful
2235 while (read(fd, &event, sizeof(event)) == (int)sizeof(event))
2236 {
2237 // Relative movement parsing
2238 if (event.type == EV_REL)
2239 {
2240 if (event.code == REL_X)
2241 {
2242 if (platform.cursorRelative)
2243 {
2244 CORE.Input.Mouse.currentPosition.x = event.value;
2245 CORE.Input.Mouse.previousPosition.x = 0.0f;
2246 }
2247 else CORE.Input.Mouse.currentPosition.x += event.value;
2248
2249 // NOTE: For DRM touchscreen, do not simulate touch from mouse movement
2250 // CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x;
2251 touchAction = 2; // TOUCH_ACTION_MOVE
2252 }
2253
2254 if (event.code == REL_Y)
2255 {
2256 if (platform.cursorRelative)
2257 {
2258 CORE.Input.Mouse.currentPosition.y = event.value;
2259 CORE.Input.Mouse.previousPosition.y = 0.0f;
2260 }
2261 else CORE.Input.Mouse.currentPosition.y += event.value;
2262
2263 // NOTE: For DRM touchscreen, do not simulate touch from mouse movement
2264 // CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y;
2265 touchAction = 2; // TOUCH_ACTION_MOVE
2266 }
2267
2268 if (event.code == REL_WHEEL) platform.eventWheelMove.y += event.value;
2269 }
2270
2271 // Absolute movement parsing
2272 if (event.type == EV_ABS)
2273 {
2274 // Basic movement
2275 if (event.code == ABS_X)
2276 {
2277 CORE.Input.Mouse.currentPosition.x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange
2278 CORE.Input.Touch.position[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange
2279
2280 touchAction = 2; // TOUCH_ACTION_MOVE
2281 }
2282
2283 if (event.code == ABS_Y)
2284 {
2285 CORE.Input.Mouse.currentPosition.y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange
2286 CORE.Input.Touch.position[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange
2287
2288 touchAction = 2; // TOUCH_ACTION_MOVE
2289 }
2290
2291 // Multitouch movement
2292 if (event.code == ABS_MT_SLOT) platform.touchSlot = event.value; // Remember the slot number for the folowing events
2293
2294 if (event.code == ABS_MT_POSITION_X)
2295 {
2296 if (platform.touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange
2297 }
2298
2299 if (event.code == ABS_MT_POSITION_Y)
2300 {
2301 if (platform.touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange
2302 }
2303
2304 if (event.code == ABS_MT_TRACKING_ID)
2305 {
2306 if ((event.value < 0) && (platform.touchSlot < MAX_TOUCH_POINTS))
2307 {
2308 // Touch has ended for this point
2309 CORE.Input.Touch.position[platform.touchSlot].x = -1;
2310 CORE.Input.Touch.position[platform.touchSlot].y = -1;
2311 }
2312 }
2313
2314 // Touchscreen tap
2315 if (event.code == ABS_PRESSURE)
2316 {
2317 int previousMouseLeftButtonState = platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT];
2318
2319 if (!event.value && previousMouseLeftButtonState)
2320 {
2321 platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0;
2322 touchAction = 0; // TOUCH_ACTION_UP
2323 }
2324
2325 if (event.value && !previousMouseLeftButtonState)
2326 {
2327 platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1;
2328 touchAction = 1; // TOUCH_ACTION_DOWN
2329 }
2330 }
2331
2332 }
2333
2334 // Button parsing
2335 if (event.type == EV_KEY)
2336 {
2337 // Mouse button parsing
2338 if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT))
2339 {
2340 platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value;
2341
2342 if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN
2343 else touchAction = 0; // TOUCH_ACTION_UP
2344 }
2345
2346 if (event.code == BTN_RIGHT) platform.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value;
2347 if (event.code == BTN_MIDDLE) platform.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value;
2348 if (event.code == BTN_SIDE) platform.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value;
2349 if (event.code == BTN_EXTRA) platform.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value;
2350 if (event.code == BTN_FORWARD) platform.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value;
2351 if (event.code == BTN_BACK) platform.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value;
2352 }
2353
2354 // Screen confinement
2355 if (!CORE.Input.Mouse.cursorLocked)
2356 {
2357 if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0;
2358 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;
2359
2360 if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0;
2361 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;
2362 }
2363
2364 // Update touch point count
2365 CORE.Input.Touch.pointCount = 0;
2366 for (int i = 0; i < MAX_TOUCH_POINTS; i++)
2367 {
2368 if (CORE.Input.Touch.position[i].x >= 0) CORE.Input.Touch.pointCount++;
2369 }
2370
2371#if defined(SUPPORT_GESTURES_SYSTEM)
2372 if (touchAction > -1)
2373 {
2374 GestureEvent gestureEvent = { 0 };
2375
2376 gestureEvent.touchAction = touchAction;
2377 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
2378
2379 for (int i = 0; i < MAX_TOUCH_POINTS; i++)
2380 {
2381 gestureEvent.pointId[i] = i;
2382 gestureEvent.position[i] = CORE.Input.Touch.position[i];
2383 }
2384
2385 ProcessGestureEvent(gestureEvent);
2386
2387 touchAction = -1;
2388 }
2389#endif
2390 }
2391}
2392
2393// Search matching DRM mode in connector's mode list
2394static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode)
2395{
2396 if (NULL == connector) return -1;
2397 if (NULL == mode) return -1;
2398
2399 // safe bitwise comparison of two modes
2400 #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b))? sizeof(a) : sizeof(b))
2401
2402 for (size_t i = 0; i < connector->count_modes; i++)
2403 {
2404 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay,
2405 connector->modes[i].vrefresh, (FLAG_IS_SET(connector->modes[i].flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive");
2406
2407 if (0 == BINCMP(&platform.crtc->mode, &platform.connector->modes[i])) return i;
2408 }
2409
2410 return -1;
2411
2412 #undef BINCMP
2413}
2414
2415// Search exactly matching DRM connector mode in connector's list
2416static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
2417{
2418 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");
2419
2420 if (NULL == connector) return -1;
2421
2422 for (int i = 0; i < platform.connector->count_modes; i++)
2423 {
2424 const drmModeModeInfo *const mode = &platform.connector->modes[i];
2425
2426 TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive");
2427
2428 if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) continue;
2429
2430 if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i;
2431 }
2432
2433 TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found");
2434 return -1;
2435}
2436
2437// Search the nearest matching DRM connector mode in connector's list
2438static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
2439{
2440 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");
2441
2442 if (NULL == connector) return -1;
2443
2444 int nearestIndex = -1;
2445 int minUnusedPixels = INT_MAX;
2446 int minFpsDiff = INT_MAX;
2447 for (int i = 0; i < platform.connector->count_modes; i++)
2448 {
2449 const drmModeModeInfo *const mode = &platform.connector->modes[i];
2450
2451 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh,
2452 (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive");
2453
2454 if ((mode->hdisplay < width) || (mode->vdisplay < height))
2455 {
2456 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small");
2457 continue;
2458 }
2459
2460 if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced)
2461 {
2462 TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode");
2463 continue;
2464 }
2465
2466 const int unusedPixels = (mode->hdisplay - width)*(mode->vdisplay - height);
2467 const int fpsDiff = mode->vrefresh - fps;
2468
2469 if ((unusedPixels < minUnusedPixels) ||
2470 ((unusedPixels == minUnusedPixels) && (abs(fpsDiff) < abs(minFpsDiff))) ||
2471 ((unusedPixels == minUnusedPixels) && (abs(fpsDiff) == abs(minFpsDiff)) && (fpsDiff > 0)))
2472 {
2473 nearestIndex = i;
2474 minUnusedPixels = unusedPixels;
2475 minFpsDiff = fpsDiff;
2476 }
2477 }
2478
2479 return nearestIndex;
2480}
2481
2482// Compute framebuffer size relative to screen size and display size
2483// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified
2484static void SetupFramebuffer(int width, int height)
2485{
2486 // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var)
2487 if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height))
2488 {
2489 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);
2490
2491 // Downscaling to fit display with border-bars
2492 float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width;
2493 float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height;
2494
2495 if (widthRatio <= heightRatio)
2496 {
2497 CORE.Window.render.width = CORE.Window.display.width;
2498 CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio);
2499 CORE.Window.renderOffset.x = 0;
2500 CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height);
2501 }
2502 else
2503 {
2504 CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio);
2505 CORE.Window.render.height = CORE.Window.display.height;
2506 CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width);
2507 CORE.Window.renderOffset.y = 0;
2508 }
2509
2510 // Screen scaling required
2511 float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width;
2512 CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f);
2513
2514 // NOTE: We render to full display resolution!
2515 // We just need to calculate above parameters for downscale matrix and offsets
2516 CORE.Window.render.width = CORE.Window.display.width;
2517 CORE.Window.render.height = CORE.Window.display.height;
2518
2519 TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height);
2520 }
2521 else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height))
2522 {
2523 // Required screen size is smaller than display size
2524 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);
2525
2526 if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0))
2527 {
2528 CORE.Window.screen.width = CORE.Window.display.width;
2529 CORE.Window.screen.height = CORE.Window.display.height;
2530 }
2531
2532 // Upscaling to fit display with border-bars
2533 float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height;
2534 float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
2535
2536 if (displayRatio <= screenRatio)
2537 {
2538 CORE.Window.render.width = CORE.Window.screen.width;
2539 CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio);
2540 CORE.Window.renderOffset.x = 0;
2541 CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height);
2542 }
2543 else
2544 {
2545 CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio);
2546 CORE.Window.render.height = CORE.Window.screen.height;
2547 CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width);
2548 CORE.Window.renderOffset.y = 0;
2549 }
2550 }
2551 else
2552 {
2553 CORE.Window.render.width = CORE.Window.screen.width;
2554 CORE.Window.render.height = CORE.Window.screen.height;
2555 CORE.Window.renderOffset.x = 0;
2556 CORE.Window.renderOffset.y = 0;
2557 }
2558}
2559
2560// EOF
2561
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit