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**********************************************************************************************/
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
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
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
67#include <xf86drm.h> // Direct Rendering Manager user-level library interface
68#include <xf86drmMode.h> // Direct Rendering Manager mode setting (KMS) interface
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
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
84 #define MAX_DRM_CACHED_BUFFERS 3
85#endif // SUPPORT_DRM_CACHE
87#ifndef EGL_OPENGL_ES3_BIT
88 #define EGL_OPENGL_ES3_BIT 0x40
89#endif
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
96#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events
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
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)
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
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
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
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;
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;
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
159//----------------------------------------------------------------------------------
160// Global Variables Definition
161//----------------------------------------------------------------------------------
162extern CoreData CORE; // Global CORE state context
164static PlatformData platform = { 0 }; // Platform specific data
166//----------------------------------------------------------------------------------
167// Global Variables Definition
168//----------------------------------------------------------------------------------
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};
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,
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};
244//----------------------------------------------------------------------------------
245// Module Internal Functions Declaration
246//----------------------------------------------------------------------------------
247int InitPlatform(void); // Initialize platform (graphics, inputs and more)
248void ClosePlatform(void); // Close platform
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
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
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
267static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform())
269//----------------------------------------------------------------------------------
270// Module Functions Declaration
271//----------------------------------------------------------------------------------
272// NOTE: Functions declaration is provided by raylib.h
274//----------------------------------------------------------------------------------
275// Module Functions Definition: Window and Graphics Device
276//----------------------------------------------------------------------------------
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}
286// Toggle fullscreen mode
287void ToggleFullscreen(void)
288{
289 TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform");
290}
292// Toggle borderless windowed mode
293void ToggleBorderlessWindowed(void)
294{
295 TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform");
296}
298// Set window state: maximized, if resizable
299void MaximizeWindow(void)
300{
301 TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform");
302}
304// Set window state: minimized
305void MinimizeWindow(void)
306{
307 TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform");
308}
310// Restore window from being minimized/maximized
311void RestoreWindow(void)
312{
313 TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform");
314}
316// Set window configuration state using flags
317void SetWindowState(unsigned int flags)
318{
319 TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform");
320}
322// Clear window configuration state flags
323void ClearWindowState(unsigned int flags)
324{
325 TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform");
326}
328// Set icon for window
329void SetWindowIcon(Image image)
330{
331 TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform");
332}
334// Set icon for window
335void SetWindowIcons(Image *images, int count)
336{
337 TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform");
338}
340// Set title for window
341void SetWindowTitle(const char *title)
342{
343 CORE.Window.title = title;
344}
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}
352// Set monitor for the current window
353void SetWindowMonitor(int monitor)
354{
355 TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform");
356}
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}
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}
372// Set window dimensions
373void SetWindowSize(int width, int height)
374{
375 TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform");
376}
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}
384// Set window focused
385void SetWindowFocused(void)
386{
387 TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform");
388}
390// Get native window handle
391void *GetWindowHandle(void)
392{
393 TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform");
394 return NULL;
395}
397// Get number of monitors
398int GetMonitorCount(void)
399{
400 TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform");
401 return 1;
402}
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}
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}
418// Get selected monitor width (currently used by monitor)
419int GetMonitorWidth(int monitor)
420{
421 int width = 0;
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 }
432 return width;
433}
435// Get selected monitor height (currently used by monitor)
436int GetMonitorHeight(int monitor)
437{
438 int height = 0;
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 }
449 return height;
450}
452// Get selected monitor physical width in millimetres
453int GetMonitorPhysicalWidth(int monitor)
454{
455 int physicalWidth = 0;
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 }
466 return physicalWidth;
467}
469// Get selected monitor physical height in millimetres
470int GetMonitorPhysicalHeight(int monitor)
471{
472 int physicalHeight = 0;
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 }
483 return physicalHeight;
484}
486// Get selected monitor refresh rate
487int GetMonitorRefreshRate(int monitor)
488{
489 int refresh = 0;
491 if ((platform.connector) && (platform.modeIndex >= 0))
492 {
493 refresh = platform.connector->modes[platform.modeIndex].vrefresh;
494 }
496 return refresh;
497}
499// Get the human-readable, UTF-8 encoded name of the selected monitor
500const char *GetMonitorName(int monitor)
501{
502 const char *name = "";
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 }
513 return name;
514}
516// Get window position XY on monitor
517Vector2 GetWindowPosition(void)
518{
519 return (Vector2){ 0, 0 };
520}
522// Get window scale DPI factor for current monitor
523Vector2 GetWindowScaleDPI(void)
524{
525 return (Vector2){ 1.0f, 1.0f };
526}
528// Set clipboard text content
529void SetClipboardText(const char *text)
530{
531 TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform");
532}
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}
542// Get clipboard image
543Image GetClipboardImage(void)
544{
545 Image image = { 0 };
547 TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform");
549 return image;
550}
552// Show mouse cursor
553void ShowCursor(void)
554{
555 CORE.Input.Mouse.cursorHidden = false;
556}
558// Hides mouse cursor
559void HideCursor(void)
560{
561 CORE.Input.Mouse.cursorHidden = true;
562}
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);
570 platform.cursorRelative = false;
571 CORE.Input.Mouse.cursorLocked = false;
572}
574// Disables cursor (lock cursor)
575void DisableCursor(void)
576{
577 // Set cursor position in the middle
578 SetMousePosition(0, 0);
580 platform.cursorRelative = true;
581 CORE.Input.Mouse.cursorLocked = true;
582}
584#if defined(SUPPORT_DRM_CACHE)
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;
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
599 // Shift remaining entries
600 for (int j = i; j < fbCacheCount - 1; j++) fbCache[j] = fbCache[j + 1];
602 fbCacheCount--;
603 break;
604 }
605 }
606}
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 }
617 // Create new entry if cache not full
618 if (fbCacheCount >= MAX_DRM_CACHED_BUFFERS) return 0; // FB cache full
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);
625 uint32_t fbId = 0;
626 if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) return 0;
628 // Store in cache
629 fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId };
630 fbCacheCount++;
632 // Set destroy callback to auto-cleanup
633 gbm_bo_set_user_data(bo, (void *)(uintptr_t)fbId, DestroyFrameBufferCallback);
635 TRACELOG(LOG_INFO, "DISPLAY: DRM: Added new buffer object [%u]" , (uintptr_t)fbId);
637 return fbId;
638}
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}
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 }
659 // Render a blank frame to allocate buffers
660 RenderBlankFrame();
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 }
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 }
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 }
686 // Keep first buffer locked until flipped
687 platform.prevBO = bo;
688 crtcSet = true;
690 return 0;
691}
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;
703 pendingFlip = false;
704 struct gbm_bo *boToRelease = (struct gbm_bo *)data;
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}
711// Swap implementation with proper caching
712void SwapScreenBuffer()
713{
714 if (!crtcSet || !platform.gbmSurface) return;
716 static int loopCnt = 0;
717 static int errCnt[5] = { 0 };
718 loopCnt++;
720 // Call this only, if pendingFlip is not set
721 eglSwapBuffers(platform.device, platform.surface);
723 // Process pending events non-blocking
724 drmEventContext evctx = {
725 .version = DRM_EVENT_CONTEXT_VERSION,
726 .page_flip_handler = PageFlipHandler
727 };
729 struct pollfd pfd = { .fd = platform.fd, .events = POLLIN };
731 // Polling for event for 0ms
732 while (poll(&pfd, 1, 0) > 0) drmHandleEvent(platform.fd, &evctx);
734 // Skip if previous flip pending
735 if (pendingFlip)
736 {
737 errCnt[0]++; // Skip frame: flip pending
738 return;
739 }
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 }
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 }
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
769 gbm_surface_release_buffer(platform.gbmSurface, nextBO);
770 return;
771 }
773 // Success: update state
774 pendingFlip = true;
775 platform.prevBO = nextBO;
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}
788#else // !SUPPORT_DRM_CACHE
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);
797 if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
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");
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);
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);
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 }
815 platform.prevFB = fb;
817 if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
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 }
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;
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
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;
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 }
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 }
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 }
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 }
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);
898 // Unmap the buffer
899 munmap(dumbBuffer, creq.size);
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 }
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);
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 }
941 if (crtc) drmModeFreeCrtc(crtc);
942 }
943 }
945 if (encoder) drmModeFreeEncoder(encoder);
946 drmModeFreeResources(res);
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 }
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);
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 }
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 }
981 platform.prevFB = fb;
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 }
991 platform.prevDumbHandle = creq.handle;
992#endif
993}
994#endif // SUPPORT_DRM_CACHE
996//----------------------------------------------------------------------------------
997// Module Functions Definition: Misc
998//----------------------------------------------------------------------------------
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;
1008 time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer()
1010 return time;
1011}
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}
1023//----------------------------------------------------------------------------------
1024// Module Functions Definition: Inputs
1025//----------------------------------------------------------------------------------
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}
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}
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}
1047// Set mouse cursor
1048void SetMouseCursor(int cursor)
1049{
1050 TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform");
1051}
1053// Get physical key name
1054const char *GetKeyName(int key)
1055{
1056 TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform");
1057 return "";
1058}
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
1069 // Reset keys/chars pressed registered
1070 CORE.Input.Keyboard.keyPressedQueueCount = 0;
1071 CORE.Input.Keyboard.charPressedQueueCount = 0;
1073 // Reset last gamepad button/axis registered state
1074 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
1075 //CORE.Input.Gamepad.axisCount = 0;
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 }
1084 PollKeyboardEvents();
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
1092 // Check exit key
1093 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
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;
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 };
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 }
1111 // Register gamepads buttons events
1112 PollGamepadEvents();
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];
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 };
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;
1124 // Handle the mouse/touch/gestures events:
1125 PollMouseEvents();
1126}
1128//----------------------------------------------------------------------------------
1129// Module Internal Functions Definition
1130//----------------------------------------------------------------------------------
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;
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
1149 // Initialize graphic device: display/window and graphic context
1150 //----------------------------------------------------------------------------
1151 FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
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");
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 }
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 }
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
1186 if (platform.fd == -1)
1187 {
1188 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card");
1189 return -1;
1190 }
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 }
1200 TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors);
1202 // Connector detection
1203 for (size_t i = 0; i < res->count_connectors; i++)
1204 {
1205 TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i);
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 }
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");
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 }
1242 if (!platform.connector)
1243 {
1244 TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i NOT suitable (deleting)", i);
1245 drmModeFreeConnector(con);
1246 }
1247 }
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 }
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 }
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 }
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...");
1284 platform.modeIndex = FindMatchingConnectorMode(platform.connector, &platform.crtc->mode);
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 }
1296 CORE.Window.screen.width = CORE.Window.display.width;
1297 CORE.Window.screen.height = CORE.Window.display.height;
1298 }
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;
1303 // Try to find an exact matching mode
1304 platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
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);
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);
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);
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 }
1326 CORE.Window.display.width = platform.connector->modes[platform.modeIndex].hdisplay;
1327 CORE.Window.display.height = platform.connector->modes[platform.modeIndex].vdisplay;
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);
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;
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
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;
1365 drmModeFreeResources(res);
1366 res = NULL;
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 }
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 }
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 }
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 };
1409 const EGLint contextAttribs[] = {
1410 EGL_CONTEXT_CLIENT_VERSION, 2,
1411 EGL_NONE
1412 };
1414 EGLint numConfigs = 0;
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 }
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 }
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 }
1438 TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs);
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 }
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 }
1455 TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs);
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 }
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 }
1477 RL_FREE(configs);
1479 if (!found)
1480 {
1481 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config");
1482 return -1;
1483 }
1485 // Set rendering API
1486 eglBindAPI(EGL_OPENGL_ES_API);
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 }
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 }
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);
1511 // There must be at least one frame displayed before the buffers are swapped
1512 //eglSwapInterval(platform.device, 1);
1514 EGLBoolean result = eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context);
1516 // Check surface and context activation
1517 if (result != EGL_FALSE)
1518 {
1519 CORE.Window.ready = true;
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;
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 }
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);
1549 // Setup window ready state for software rendering
1550 CORE.Window.ready = true;
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;
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
1564 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow();
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);
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
1576 //----------------------------------------------------------------------------
1577 // Initialize timing system
1578 //----------------------------------------------------------------------------
1579 // NOTE: timming system must be initialized before the input events system
1580 InitTimer();
1581 //----------------------------------------------------------------------------
1583 // Initialize input events system
1584 //----------------------------------------------------------------------------
1585 InitEvdevInput(); // Evdev inputs initialization
1587#if defined(SUPPORT_SSH_KEYBOARD_RPI)
1588 InitKeyboard(); // Keyboard init (stdin)
1589#endif
1590 //----------------------------------------------------------------------------
1592 // Initialize storage system
1593 //----------------------------------------------------------------------------
1594 CORE.Storage.basePath = GetWorkingDirectory();
1595 //----------------------------------------------------------------------------
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}
1614// Close platform
1615void ClosePlatform(void)
1616{
1617 if (platform.prevFB)
1618 {
1619 drmModeRmFB(platform.fd, platform.prevFB);
1620 platform.prevFB = 0;
1621 }
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 }
1630 if (platform.gbmSurface)
1631 {
1632 gbm_surface_destroy(platform.gbmSurface);
1633 platform.gbmSurface = NULL;
1634 }
1636 if (platform.gbmDevice)
1637 {
1638 gbm_device_destroy(platform.gbmDevice);
1639 platform.gbmDevice = NULL;
1640 }
1641#endif
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 }
1653 drmModeFreeCrtc(platform.crtc);
1654 platform.crtc = NULL;
1655 }
1657 if (platform.fd != -1)
1658 {
1659 close(platform.fd);
1660 platform.fd = -1;
1661 }
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 }
1673 if (platform.context != EGL_NO_CONTEXT)
1674 {
1675 eglDestroyContext(platform.device, platform.context);
1676 platform.context = EGL_NO_CONTEXT;
1677 }
1679 eglTerminate(platform.device);
1680 platform.device = EGL_NO_DISPLAY;
1681 }
1682#endif
1684 CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called
1686 // Close the evdev devices
1688 if (platform.mouseFd != -1)
1689 {
1690 close(platform.mouseFd);
1691 platform.mouseFd = -1;
1692 }
1694 for (int i = 0; i < platform.gamepadCount; i++)
1695 {
1696 close(platform.gamepadStreamFd[i]);
1697 platform.gamepadStreamFd[i] = -1;
1698 }
1700 if (platform.keyboardFd != -1)
1701 {
1702 close(platform.keyboardFd);
1703 platform.keyboardFd = -1;
1704 }
1705}
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
1714 // Save terminal keyboard settings
1715 tcgetattr(STDIN_FILENO, &platform.defaultSettings);
1717 // Reconfigure terminal with new settings
1718 struct termios keyboardNewSettings = { 0 };
1719 keyboardNewSettings = platform.defaultSettings;
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;
1728 // Set new keyboard settings (change occurs immediately)
1729 tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings);
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
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);
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 }
1750 // Register keyboard restore when program finishes
1751 atexit(RestoreKeyboard);
1752}
1754// Restore default keyboard input
1755static void RestoreKeyboard(void)
1756{
1757 // Reset to default keyboard settings
1758 tcsetattr(STDIN_FILENO, TCSANOW, &platform.defaultSettings);
1760 // Reconfigure keyboard to default mode
1761 fcntl(STDIN_FILENO, F_SETFL, platform.defaultFileFlags);
1762 ioctl(STDIN_FILENO, KDSKBMODE, platform.defaultKeyboardMode);
1763}
1765// Process keyboard inputs
1766static void ProcessKeyboard(void)
1767{
1768 #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read
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
1774 // Read availables keycodes from stdin
1775 bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call
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 }
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 }
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 }
1831 i += 3; // Jump to next key
1832 }
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;
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;
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;
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
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;
1876 // Initialize keyboard file descriptor
1877 platform.keyboardFd = -1;
1878 platform.mouseFd = -1;
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 }
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 }
1894 // Open the linux directory of "/dev/input"
1895 directory = opendir(DEFAULT_EVDEV_PATH);
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 }
1909 closedir(directory);
1910 }
1911 else TRACELOG(LOG_WARNING, "INPUT: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH);
1912}
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)
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 };
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 }
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 //-------------------------------------------------------------------------------------------------------
1941 // Identify the device
1942 //-------------------------------------------------------------------------------------------------------
1943 struct {
1944 bool exist;
1945 struct input_absinfo info;
1946 } absinfo[ABS_CNT] = { 0 };
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;
1956 int absAxisCount = 0;
1958 ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits);
1959 ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits);
1961 if (TEST_BIT(evBits, EV_ABS))
1962 {
1963 ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits);
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);
1968 if (hasAbsXY)
1969 {
1970 absAxisCount += 2;
1972 absinfo[ABS_X].exist = true;
1973 absinfo[ABS_Y].exist = true;
1975 ioctl(fd, EVIOCGABS(ABS_X), &absinfo[ABS_X].info);
1976 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo[ABS_Y].info);
1977 }
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;
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;
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++;
2001 ioctl(fd, EVIOCGABS(axis), &absinfo[axis].info);
2002 }
2003 }
2004 }
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 }
2011 if (TEST_BIT(evBits, EV_REL))
2012 {
2013 ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits);
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 }
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;
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 }
2039 for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40; button++)
2040 {
2041 if (TEST_BIT(keyBits, button)) isGamepad = true;
2042 }
2043 }
2044 }
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;
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;
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++;
2067 platform.gamepadStreamFd[index] = fd;
2068 CORE.Input.Gamepad.ready[index] = true;
2070 ioctl(platform.gamepadStreamFd[index], EVIOCGNAME(64), &CORE.Input.Gamepad.name[index]);
2071 CORE.Input.Gamepad.axisCount[index] = absAxisCount;
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;
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 }
2108 TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr);
2109}
2111// Poll and process evdev keyboard events
2112static void PollKeyboardEvents(void)
2113{
2114 int fd = platform.keyboardFd;
2115 if (fd == -1) return;
2117 struct input_event event = { 0 };
2118 int keycode = -1;
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;
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
2131 // Keyboard keys appear for codes 1 to 255, ignore everthing else
2132 if ((event.code >= 1) && (event.code <= 255))
2133 {
2135 // Lookup the scancode in the keymap to get a keycode
2136 keycode = linuxToRaylibMap[event.code];
2138 // Make sure we got a valid keycode
2139 if ((keycode > 0) && (keycode < MAX_KEYBOARD_KEYS))
2140 {
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);
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 }
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 }
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}
2171// Poll gamepad input events
2172static void PollGamepadEvents(void)
2173{
2174 // Read gamepad event
2175 struct input_event event = { 0 };
2177 for (int i = 0; i < platform.gamepadCount; i++)
2178 {
2179 if (!CORE.Input.Gamepad.ready[i]) continue;
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];
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];
2192 TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: KEY_%s Keycode(linux): %4i Keycode(raylib): %4i", i, (event.value == 0)? "UP" : "DOWN", event.code, keycodeRaylib);
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;
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];
2209 TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: Axis: %2i Value: %i", i, axisRaylib, event.value);
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];
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}
2225// Poll mouse input events
2226static void PollMouseEvents(void)
2227{
2228 int fd = platform.mouseFd;
2229 if (fd == -1) return;
2231 struct input_event event = { 0 };
2232 int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE
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;
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 }
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;
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 }
2268 if (event.code == REL_WHEEL) platform.eventWheelMove.y += event.value;
2269 }
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
2280 touchAction = 2; // TOUCH_ACTION_MOVE
2281 }
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
2288 touchAction = 2; // TOUCH_ACTION_MOVE
2289 }
2291 // Multitouch movement
2292 if (event.code == ABS_MT_SLOT) platform.touchSlot = event.value; // Remember the slot number for the folowing events
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 }
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 }
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 }
2314 // Touchscreen tap
2315 if (event.code == ABS_PRESSURE)
2316 {
2317 int previousMouseLeftButtonState = platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT];
2319 if (!event.value && previousMouseLeftButtonState)
2320 {
2321 platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0;
2322 touchAction = 0; // TOUCH_ACTION_UP
2323 }
2325 if (event.value && !previousMouseLeftButtonState)
2326 {
2327 platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1;
2328 touchAction = 1; // TOUCH_ACTION_DOWN
2329 }
2330 }
2332 }
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;
2342 if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN
2343 else touchAction = 0; // TOUCH_ACTION_UP
2344 }
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 }
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;
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 }
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 }
2371#if defined(SUPPORT_GESTURES_SYSTEM)
2372 if (touchAction > -1)
2373 {
2374 GestureEvent gestureEvent = { 0 };
2376 gestureEvent.touchAction = touchAction;
2377 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
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 }
2385 ProcessGestureEvent(gestureEvent);
2387 touchAction = -1;
2388 }
2389#endif
2390 }
2391}
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;
2399 // safe bitwise comparison of two modes
2400 #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b))? sizeof(a) : sizeof(b))
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");
2407 if (0 == BINCMP(&platform.crtc->mode, &platform.connector->modes[i])) return i;
2408 }
2410 return -1;
2412 #undef BINCMP
2413}
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");
2420 if (NULL == connector) return -1;
2422 for (int i = 0; i < platform.connector->count_modes; i++)
2423 {
2424 const drmModeModeInfo *const mode = &platform.connector->modes[i];
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");
2428 if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) continue;
2430 if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i;
2431 }
2433 TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found");
2434 return -1;
2435}
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");
2442 if (NULL == connector) return -1;
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];
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");
2454 if ((mode->hdisplay < width) || (mode->vdisplay < height))
2455 {
2456 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small");
2457 continue;
2458 }
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 }
2466 const int unusedPixels = (mode->hdisplay - width)*(mode->vdisplay - height);
2467 const int fpsDiff = mode->vrefresh - fps;
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 }
2479 return nearestIndex;
2480}
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);
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;
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 }
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);
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;
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);
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 }
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;
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}
2560// EOF
index : raylib-jai
---