From df6260d23b71dca50a7df52d540ffb31cdf020ab Mon Sep 17 00:00:00 2001 From: MichaelFiber Date: Sun, 24 Sep 2023 12:53:34 -0400 Subject: [PATCH] Move drm functions to rcore_drm.c --- src/rcore.c | 872 ------------------------------------------------ src/rcore_drm.c | 865 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 865 insertions(+), 872 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index a5bd8839a..3111477d4 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -309,27 +309,6 @@ static void SetupViewport(int width, int height); // Set viewport for a pr static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path -#if defined(PLATFORM_DRM) -static void InitKeyboard(void); // Initialize raw keyboard system -static void RestoreKeyboard(void); // Restore keyboard system -#if defined(SUPPORT_SSH_KEYBOARD_RPI) -static void ProcessKeyboard(void); // Process keyboard events -#endif - -static void InitEvdevInput(void); // Initialize evdev inputs -static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate -static void PollKeyboardEvents(void); // Process evdev keyboard events. -static void *EventThread(void *arg); // Input device events reading thread - -static void InitGamepad(void); // Initialize raw gamepad input -static void *GamepadThread(void *arg); // Mouse reading thread - -static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list -static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list -static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list - -#endif // PLATFORM_DRM - #if defined(SUPPORT_EVENTS_AUTOMATION) static void LoadAutomationEvents(const char *fileName); // Load automation events from file static void ExportAutomationEvents(const char *fileName); // Export recorded automation events into a file @@ -2720,857 +2699,6 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths } #endif -#if defined(PLATFORM_DRM) -// Initialize Keyboard system (using standard input) -static void InitKeyboard(void) -{ - // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor, - // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE - - // Save terminal keyboard settings - tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); - - // Reconfigure terminal with new settings - struct termios keyboardNewSettings = { 0 }; - keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; - - // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing - // NOTE: ISIG controls if ^C and ^Z generate break signals or not - keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); - //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); - keyboardNewSettings.c_cc[VMIN] = 1; - keyboardNewSettings.c_cc[VTIME] = 0; - - // Set new keyboard settings (change occurs immediately) - tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); - - // Save old keyboard mode to restore it at the end - CORE.Input.Keyboard.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags - fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified - - // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno) - int result = ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode); - - // In case of failure, it could mean a remote keyboard is used (SSH) - if (result < 0) TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode, an SSH keyboard is probably used"); - else - { - // Reconfigure keyboard mode to get: - // - scancodes (K_RAW) - // - keycodes (K_MEDIUMRAW) - // - ASCII chars (K_XLATE) - // - UNICODE chars (K_UNICODE) - ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars - } - - // Register keyboard restore when program finishes - atexit(RestoreKeyboard); -} - -// Restore default keyboard input -static void RestoreKeyboard(void) -{ - // Reset to default keyboard settings - tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); - - // Reconfigure keyboard to default mode - fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags); - ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); -} - -#if defined(SUPPORT_SSH_KEYBOARD_RPI) -// Process keyboard inputs -static void ProcessKeyboard(void) -{ - #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read - - // Keyboard input polling (fill keys[256] array with status) - int bufferByteCount = 0; // Bytes available on the buffer - char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time - - // Read availables keycodes from stdin - bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call - - // Reset pressed keys array (it will be filled below) - for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) - { - CORE.Input.Keyboard.currentKeyState[i] = 0; - CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; - } - - // Fill all read bytes (looking for keys) - for (int i = 0; i < bufferByteCount; i++) - { - // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! - // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 - if (keysBuffer[i] == 0x1b) - { - // Check if ESCAPE key has been pressed to stop program - if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; - else - { - if (keysBuffer[i + 1] == 0x5b) // Special function key - { - if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) - { - // Process special function keys (F1 - F12) - switch (keysBuffer[i + 3]) - { - case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 - case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 - case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 - case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 - case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 - case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 - case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 - case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 - case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 - case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 - case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 - case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 - default: break; - } - - if (keysBuffer[i + 2] == 0x5b) i += 4; - else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; - } - else - { - switch (keysBuffer[i + 2]) - { - case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP - case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN - case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT - case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT - default: break; - } - - i += 3; // Jump to next key - } - - // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) - } - } - } - else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) - { - CORE.Input.Keyboard.currentKeyState[257] = 1; - - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue - CORE.Input.Keyboard.keyPressedQueueCount++; - } - else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE - { - CORE.Input.Keyboard.currentKeyState[259] = 1; - - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue - CORE.Input.Keyboard.keyPressedQueueCount++; - } - else - { - // Translate lowercase a-z letters to A-Z - if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) - { - CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; - } - else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; - - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue - CORE.Input.Keyboard.keyPressedQueueCount++; - } - } - - // Check exit key (same functionality as GLFW3 KeyCallback()) - if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; - -#if defined(SUPPORT_SCREEN_CAPTURE) - // Check screen capture key (raylib key: KEY_F12) - if (CORE.Input.Keyboard.currentKeyState[301] == 1) - { - TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); - screenshotCounter++; - } -#endif -} -#endif // SUPPORT_SSH_KEYBOARD_RPI - -// Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices -static void InitEvdevInput(void) -{ - char path[MAX_FILEPATH_LENGTH] = { 0 }; - DIR *directory = NULL; - struct dirent *entity = NULL; - - // Initialise keyboard file descriptor - CORE.Input.Keyboard.fd = -1; - - // Reset variables - for (int i = 0; i < MAX_TOUCH_POINTS; ++i) - { - CORE.Input.Touch.position[i].x = -1; - CORE.Input.Touch.position[i].y = -1; - } - - // Reset keyboard key state - for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) - { - CORE.Input.Keyboard.currentKeyState[i] = 0; - CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; - } - - // Open the linux directory of "/dev/input" - directory = opendir(DEFAULT_EVDEV_PATH); - - if (directory) - { - while ((entity = readdir(directory)) != NULL) - { - if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" - (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" - { - sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); - ConfigureEvdevDevice(path); // Configure the device if appropriate - } - } - - closedir(directory); - } - else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); -} - -// Identifies a input device and configures it for use if appropriate -static void ConfigureEvdevDevice(char *device) -{ - #define BITS_PER_LONG (8*sizeof(long)) - #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) - #define OFF(x) ((x)%BITS_PER_LONG) - #define BIT(x) (1UL<> OFF(bit)) & 1) - - struct input_absinfo absinfo = { 0 }; - unsigned long evBits[NBITS(EV_MAX)] = { 0 }; - unsigned long absBits[NBITS(ABS_MAX)] = { 0 }; - unsigned long relBits[NBITS(REL_MAX)] = { 0 }; - unsigned long keyBits[NBITS(KEY_MAX)] = { 0 }; - bool hasAbs = false; - bool hasRel = false; - bool hasAbsMulti = false; - int freeWorkerId = -1; - int fd = -1; - - InputEventWorker *worker = NULL; - - // Open the device and allocate worker - //------------------------------------------------------------------------------------------------------- - // Find a free spot in the workers array - for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) - { - if (CORE.Input.eventWorker[i].threadId == 0) - { - freeWorkerId = i; - break; - } - } - - // Select the free worker from array - if (freeWorkerId >= 0) - { - worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker - memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker - } - else - { - TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); - return; - } - - // Open the device - fd = open(device, O_RDONLY | O_NONBLOCK); - if (fd < 0) - { - TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device); - return; - } - worker->fd = fd; - - // Grab number on the end of the devices name "event" - int devNum = 0; - char *ptrDevName = strrchr(device, 't'); - worker->eventNum = -1; - - if (ptrDevName != NULL) - { - if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum; - } - else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device! - - // At this point we have a connection to the device, but we don't yet know what the device is. - // It could be many things, even as simple as a power button... - //------------------------------------------------------------------------------------------------------- - - // Identify the device - //------------------------------------------------------------------------------------------------------- - ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties - - // Check for absolute input devices - if (TEST_BIT(evBits, EV_ABS)) - { - ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); - - // Check for absolute movement support (usually touchscreens, but also joysticks) - if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) - { - hasAbs = true; - - // Get the scaling values - ioctl(fd, EVIOCGABS(ABS_X), &absinfo); - worker->absRange.x = absinfo.minimum; - worker->absRange.width = absinfo.maximum - absinfo.minimum; - ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); - worker->absRange.y = absinfo.minimum; - worker->absRange.height = absinfo.maximum - absinfo.minimum; - } - - // Check for multiple absolute movement support (usually multitouch touchscreens) - if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) - { - hasAbsMulti = true; - - // Get the scaling values - ioctl(fd, EVIOCGABS(ABS_X), &absinfo); - worker->absRange.x = absinfo.minimum; - worker->absRange.width = absinfo.maximum - absinfo.minimum; - ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); - worker->absRange.y = absinfo.minimum; - worker->absRange.height = absinfo.maximum - absinfo.minimum; - } - } - - // Check for relative movement support (usually mouse) - if (TEST_BIT(evBits, EV_REL)) - { - ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); - - if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; - } - - // Check for button support to determine the device type(usually on all input devices) - if (TEST_BIT(evBits, EV_KEY)) - { - ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); - - if (hasAbs || hasAbsMulti) - { - if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen - if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet - if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet - if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet - if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device - } - - if (hasRel) - { - if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse - if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse - } - - if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad - if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad - if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad - if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad - if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad - - if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard - } - //------------------------------------------------------------------------------------------------------- - - // Decide what to do with the device - //------------------------------------------------------------------------------------------------------- - if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1)) - { - // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a - // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate - // threads so that they don't drop events when the frame rate is slow. - TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); - CORE.Input.Keyboard.fd = worker->fd; - } - else if (worker->isTouch || worker->isMouse) - { - // Looks like an interesting device - TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, - worker->isMouse? "mouse " : "", - worker->isMultitouch? "multitouch " : "", - worker->isTouch? "touchscreen " : "", - worker->isGamepad? "gamepad " : ""); - - // Create a thread for this device - int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); - if (error != 0) - { - TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); - worker->threadId = 0; - close(fd); - } - -#if defined(USE_LAST_TOUCH_DEVICE) - // Find touchscreen with the highest index - int maxTouchNumber = -1; - - for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) - { - if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; - } - - // Find touchscreens with lower indexes - for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) - { - if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) - { - if (CORE.Input.eventWorker[i].threadId != 0) - { - TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); - pthread_cancel(CORE.Input.eventWorker[i].threadId); - close(CORE.Input.eventWorker[i].fd); - } - } - } -#endif - } - else close(fd); // We are not interested in this device - //------------------------------------------------------------------------------------------------------- -} - -static void PollKeyboardEvents(void) -{ - // Scancode to keycode mapping for US keyboards - // TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard: - // Currently non US keyboards will have the wrong mapping for some keys - static const int keymapUS[] = { - 0, 256, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 259, 258, 81, 87, 69, 82, 84, - 89, 85, 73, 79, 80, 91, 93, 257, 341, 65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 96, - 340, 92, 90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 344, 332, 342, 32, 280, 290, 291, - 292, 293, 294, 295, 296, 297, 298, 299, 282, 281, 327, 328, 329, 333, 324, 325, - 326, 334, 321, 322, 323, 320, 330, 0, 85, 86, 300, 301, 89, 90, 91, 92, 93, 94, 95, - 335, 345, 331, 283, 346, 101, 268, 265, 266, 263, 262, 269, 264, 267, 260, 261, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 347, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 0, 0, 0, 0, 0, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, - 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, - 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, - 243, 244, 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0 - }; - - int fd = CORE.Input.Keyboard.fd; - if (fd == -1) return; - - struct input_event event = { 0 }; - int keycode = -1; - - // Try to read data from the keyboard and only continue if successful - while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) - { - // Button parsing - if (event.type == EV_KEY) - { -#if defined(SUPPORT_SSH_KEYBOARD_RPI) - // Change keyboard mode to events - CORE.Input.Keyboard.evtMode = true; -#endif - // Keyboard button parsing - if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 - { - keycode = keymapUS[event.code & 0xFF]; // The code we get is a scancode so we look up the appropriate keycode - - // Make sure we got a valid keycode - if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) - { - // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt - // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, - // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat - CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; - if (event.value >= 1) - { - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed - CORE.Input.Keyboard.keyPressedQueueCount++; - } - - #if defined(SUPPORT_SCREEN_CAPTURE) - // Check screen capture key (raylib key: KEY_F12) - if (CORE.Input.Keyboard.currentKeyState[301] == 1) - { - TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); - screenshotCounter++; - } - #endif - - if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; - - TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); - } - } - } - } -} - -// Input device events reading thread -static void *EventThread(void *arg) -{ - struct input_event event = { 0 }; - InputEventWorker *worker = (InputEventWorker *)arg; - - int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE - bool gestureUpdate = false; // Flag to note gestures require to update - - while (!CORE.Window.shouldClose) - { - // Try to read data from the device and only continue if successful - while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) - { - // Relative movement parsing - if (event.type == EV_REL) - { - if (event.code == REL_X) - { - CORE.Input.Mouse.currentPosition.x += event.value; - CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x; - - touchAction = 2; // TOUCH_ACTION_MOVE - gestureUpdate = true; - } - - if (event.code == REL_Y) - { - CORE.Input.Mouse.currentPosition.y += event.value; - CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y; - - touchAction = 2; // TOUCH_ACTION_MOVE - gestureUpdate = true; - } - - if (event.code == REL_WHEEL) CORE.Input.Mouse.eventWheelMove.y += event.value; - } - - // Absolute movement parsing - if (event.type == EV_ABS) - { - // Basic movement - if (event.code == ABS_X) - { - CORE.Input.Mouse.currentPosition.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange - CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange - - touchAction = 2; // TOUCH_ACTION_MOVE - gestureUpdate = true; - } - - if (event.code == ABS_Y) - { - CORE.Input.Mouse.currentPosition.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange - CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange - - touchAction = 2; // TOUCH_ACTION_MOVE - gestureUpdate = true; - } - - // Multitouch movement - if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events - - if (event.code == ABS_MT_POSITION_X) - { - if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange - } - - if (event.code == ABS_MT_POSITION_Y) - { - if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange - } - - if (event.code == ABS_MT_TRACKING_ID) - { - if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) - { - // Touch has ended for this point - CORE.Input.Touch.position[worker->touchSlot].x = -1; - CORE.Input.Touch.position[worker->touchSlot].y = -1; - } - } - - // Touchscreen tap - if (event.code == ABS_PRESSURE) - { - int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; - - if (!event.value && previousMouseLeftButtonState) - { - CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; - - touchAction = 0; // TOUCH_ACTION_UP - gestureUpdate = true; - } - - if (event.value && !previousMouseLeftButtonState) - { - CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; - - touchAction = 1; // TOUCH_ACTION_DOWN - gestureUpdate = true; - } - } - - } - - // Button parsing - if (event.type == EV_KEY) - { - // Mouse button parsing - if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) - { - CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; - - if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN - else touchAction = 0; // TOUCH_ACTION_UP - gestureUpdate = true; - } - - if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; - if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; - if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; - if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; - if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; - if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; - } - - // Screen confinement - if (!CORE.Input.Mouse.cursorHidden) - { - if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; - 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; - - if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; - 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; - } - - // Update touch point count - CORE.Input.Touch.pointCount = 0; - for (int i = 0; i < MAX_TOUCH_POINTS; i++) - { - if (CORE.Input.Touch.position[i].x >= 0) CORE.Input.Touch.pointCount++; - } - -#if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_DRM - if (gestureUpdate) - { - GestureEvent gestureEvent = { 0 }; - - gestureEvent.touchAction = touchAction; - gestureEvent.pointCount = CORE.Input.Touch.pointCount; - - for (int i = 0; i < MAX_TOUCH_POINTS; i++) - { - gestureEvent.pointId[i] = i; - gestureEvent.position[i] = CORE.Input.Touch.position[i]; - } - - ProcessGestureEvent(gestureEvent); - } -#endif - } - - WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time - } - - close(worker->fd); - - return NULL; -} - -// Initialize gamepad system -static void InitGamepad(void) -{ - char gamepadDev[128] = { 0 }; - - for (int i = 0; i < MAX_GAMEPADS; i++) - { - sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); - - if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0) - { - // NOTE: Only show message for first gamepad - if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); - } - else - { - CORE.Input.Gamepad.ready[i] = true; - - // NOTE: Only create one thread - if (i == 0) - { - int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); - - if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); - else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); - } - } - } -} - -// Process Gamepad (/dev/input/js0) -static void *GamepadThread(void *arg) -{ - #define JS_EVENT_BUTTON 0x01 // Button pressed/released - #define JS_EVENT_AXIS 0x02 // Joystick axis moved - #define JS_EVENT_INIT 0x80 // Initial state of device - - struct js_event { - unsigned int time; // event timestamp in milliseconds - short value; // event value - unsigned char type; // event type - unsigned char number; // event axis/button number - }; - - // Read gamepad event - struct js_event gamepadEvent = { 0 }; - - while (!CORE.Window.shouldClose) - { - for (int i = 0; i < MAX_GAMEPADS; i++) - { - if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) - { - gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events - - // Process gamepad events by type - if (gamepadEvent.type == JS_EVENT_BUTTON) - { - //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - - if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) - { - // 1 - button pressed, 0 - button released - CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value; - - if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; - else CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN - } - } - else if (gamepadEvent.type == JS_EVENT_AXIS) - { - //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - - if (gamepadEvent.number < MAX_GAMEPAD_AXIS) - { - // NOTE: Scaling of gamepadEvent.value to get values between -1..1 - CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; - } - } - } - else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time - } - } - - return NULL; -} -#endif // PLATFORM_DRM - -#if defined(PLATFORM_DRM) -// Search matching DRM mode in connector's mode list -static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) -{ - if (NULL == connector) return -1; - if (NULL == mode) return -1; - - // safe bitwise comparison of two modes - #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) - - for (size_t i = 0; i < connector->count_modes; i++) - { - TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, - connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); - - if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; - } - - return -1; - - #undef BINCMP -} - -// Search exactly matching DRM connector mode in connector's list -static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) -{ - 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"); - - if (NULL == connector) return -1; - - for (int i = 0; i < CORE.Window.connector->count_modes; i++) - { - const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; - - TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); - - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; - - if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; - } - - TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); - return -1; -} - -// Search the nearest matching DRM connector mode in connector's list -static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) -{ - 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"); - - if (NULL == connector) return -1; - - int nearestIndex = -1; - for (int i = 0; i < CORE.Window.connector->count_modes; i++) - { - const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; - - TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, - (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); - - if ((mode->hdisplay < width) || (mode->vdisplay < height)) - { - TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); - continue; - } - - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) - { - TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); - continue; - } - - if (nearestIndex < 0) - { - nearestIndex = i; - continue; - } - - const int widthDiff = abs(mode->hdisplay - width); - const int heightDiff = abs(mode->vdisplay - height); - const int fpsDiff = abs(mode->vrefresh - fps); - - const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width); - const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height); - const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps); - - if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) { - nearestIndex = i; - } - } - - return nearestIndex; -} -#endif - #if defined(SUPPORT_EVENTS_AUTOMATION) // NOTE: Loading happens over AutomationEvent *events // TODO: This system should probably be redesigned diff --git a/src/rcore_drm.c b/src/rcore_drm.c index 4fdc73296..37e5abd9e 100644 --- a/src/rcore_drm.c +++ b/src/rcore_drm.c @@ -5,6 +5,23 @@ #include "rcore.h" static bool InitGraphicsDevice(int width, int height); // Initialize graphics device +static void InitKeyboard(void); // Initialize raw keyboard system +static void RestoreKeyboard(void); // Restore keyboard system +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +static void ProcessKeyboard(void); // Process keyboard events +#endif + +static void InitEvdevInput(void); // Initialize evdev inputs +static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate +static void PollKeyboardEvents(void); // Process evdev keyboard events. +static void *EventThread(void *arg); // Input device events reading thread + +static void InitGamepad(void); // Initialize raw gamepad input +static void *GamepadThread(void *arg); // Mouse reading thread + +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization @@ -1087,4 +1104,852 @@ void PollInputEvents(void) // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() #endif +} + + +// Initialize Keyboard system (using standard input) +static void InitKeyboard(void) +{ + // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor, + // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE + + // Save terminal keyboard settings + tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure terminal with new settings + struct termios keyboardNewSettings = { 0 }; + keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // Save old keyboard mode to restore it at the end + CORE.Input.Keyboard.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags + fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified + + // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno) + int result = ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode); + + // In case of failure, it could mean a remote keyboard is used (SSH) + if (result < 0) TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode, an SSH keyboard is probably used"); + else + { + // Reconfigure keyboard mode to get: + // - scancodes (K_RAW) + // - keycodes (K_MEDIUMRAW) + // - ASCII chars (K_XLATE) + // - UNICODE chars (K_UNICODE) + ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars + } + + // Register keyboard restore when program finishes + atexit(RestoreKeyboard); +} + +// Restore default keyboard input +static void RestoreKeyboard(void) +{ + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure keyboard to default mode + fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags); + ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); +} + +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +// Process keyboard inputs +static void ProcessKeyboard(void) +{ + #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read + + // Keyboard input polling (fill keys[256] array with status) + int bufferByteCount = 0; // Bytes available on the buffer + char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time + + // Read availables keycodes from stdin + bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call + + // Reset pressed keys array (it will be filled below) + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.currentKeyState[i] = 0; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Fill all read bytes (looking for keys) + for (int i = 0; i < bufferByteCount; i++) + { + // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! + // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 + if (keysBuffer[i] == 0x1b) + { + // Check if ESCAPE key has been pressed to stop program + if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; + else + { + if (keysBuffer[i + 1] == 0x5b) // Special function key + { + if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) + { + // Process special function keys (F1 - F12) + switch (keysBuffer[i + 3]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 + case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 + case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 + case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 + case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 + case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 + case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 + case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 + case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 + case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 + case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 + case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 + default: break; + } + + if (keysBuffer[i + 2] == 0x5b) i += 4; + else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; + } + else + { + switch (keysBuffer[i + 2]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP + case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN + case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT + case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT + default: break; + } + + i += 3; // Jump to next key + } + + // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) + } + } + } + else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) + { + CORE.Input.Keyboard.currentKeyState[257] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE + { + CORE.Input.Keyboard.currentKeyState[259] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else + { + // Translate lowercase a-z letters to A-Z + if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) + { + CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; + } + else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } + + // Check exit key (same functionality as GLFW3 KeyCallback()) + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + +#if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } +#endif +} +#endif // SUPPORT_SSH_KEYBOARD_RPI + +// Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices +static void InitEvdevInput(void) +{ + char path[MAX_FILEPATH_LENGTH] = { 0 }; + DIR *directory = NULL; + struct dirent *entity = NULL; + + // Initialise keyboard file descriptor + CORE.Input.Keyboard.fd = -1; + + // Reset variables + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + } + + // Reset keyboard key state + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.currentKeyState[i] = 0; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Open the linux directory of "/dev/input" + directory = opendir(DEFAULT_EVDEV_PATH); + + if (directory) + { + while ((entity = readdir(directory)) != NULL) + { + if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" + (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" + { + sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + ConfigureEvdevDevice(path); // Configure the device if appropriate + } + } + + closedir(directory); + } + else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); +} + +// Identifies a input device and configures it for use if appropriate +static void ConfigureEvdevDevice(char *device) +{ + #define BITS_PER_LONG (8*sizeof(long)) + #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) + #define OFF(x) ((x)%BITS_PER_LONG) + #define BIT(x) (1UL<> OFF(bit)) & 1) + + struct input_absinfo absinfo = { 0 }; + unsigned long evBits[NBITS(EV_MAX)] = { 0 }; + unsigned long absBits[NBITS(ABS_MAX)] = { 0 }; + unsigned long relBits[NBITS(REL_MAX)] = { 0 }; + unsigned long keyBits[NBITS(KEY_MAX)] = { 0 }; + bool hasAbs = false; + bool hasRel = false; + bool hasAbsMulti = false; + int freeWorkerId = -1; + int fd = -1; + + InputEventWorker *worker = NULL; + + // Open the device and allocate worker + //------------------------------------------------------------------------------------------------------- + // Find a free spot in the workers array + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].threadId == 0) + { + freeWorkerId = i; + break; + } + } + + // Select the free worker from array + if (freeWorkerId >= 0) + { + worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker + memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker + } + else + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); + return; + } + + // Open the device + fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device); + return; + } + worker->fd = fd; + + // Grab number on the end of the devices name "event" + int devNum = 0; + char *ptrDevName = strrchr(device, 't'); + worker->eventNum = -1; + + if (ptrDevName != NULL) + { + if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum; + } + else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device! + + // At this point we have a connection to the device, but we don't yet know what the device is. + // It could be many things, even as simple as a power button... + //------------------------------------------------------------------------------------------------------- + + // Identify the device + //------------------------------------------------------------------------------------------------------- + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties + + // Check for absolute input devices + if (TEST_BIT(evBits, EV_ABS)) + { + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); + + // Check for absolute movement support (usually touchscreens, but also joysticks) + if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) + { + hasAbs = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + + // Check for multiple absolute movement support (usually multitouch touchscreens) + if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) + { + hasAbsMulti = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + } + + // Check for relative movement support (usually mouse) + if (TEST_BIT(evBits, EV_REL)) + { + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); + + if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; + } + + // Check for button support to determine the device type(usually on all input devices) + if (TEST_BIT(evBits, EV_KEY)) + { + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); + + if (hasAbs || hasAbsMulti) + { + if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen + if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet + if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device + } + + if (hasRel) + { + if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse + if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse + } + + if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + + if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard + } + //------------------------------------------------------------------------------------------------------- + + // Decide what to do with the device + //------------------------------------------------------------------------------------------------------- + if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1)) + { + // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a + // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate + // threads so that they don't drop events when the frame rate is slow. + TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); + CORE.Input.Keyboard.fd = worker->fd; + } + else if (worker->isTouch || worker->isMouse) + { + // Looks like an interesting device + TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, + worker->isMouse? "mouse " : "", + worker->isMultitouch? "multitouch " : "", + worker->isTouch? "touchscreen " : "", + worker->isGamepad? "gamepad " : ""); + + // Create a thread for this device + int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); + if (error != 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); + worker->threadId = 0; + close(fd); + } + +#if defined(USE_LAST_TOUCH_DEVICE) + // Find touchscreen with the highest index + int maxTouchNumber = -1; + + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; + } + + // Find touchscreens with lower indexes + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) + { + if (CORE.Input.eventWorker[i].threadId != 0) + { + TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); + pthread_cancel(CORE.Input.eventWorker[i].threadId); + close(CORE.Input.eventWorker[i].fd); + } + } + } +#endif + } + else close(fd); // We are not interested in this device + //------------------------------------------------------------------------------------------------------- +} + +static void PollKeyboardEvents(void) +{ + // Scancode to keycode mapping for US keyboards + // TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard: + // Currently non US keyboards will have the wrong mapping for some keys + static const int keymapUS[] = { + 0, 256, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 259, 258, 81, 87, 69, 82, 84, + 89, 85, 73, 79, 80, 91, 93, 257, 341, 65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 96, + 340, 92, 90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 344, 332, 342, 32, 280, 290, 291, + 292, 293, 294, 295, 296, 297, 298, 299, 282, 281, 327, 328, 329, 333, 324, 325, + 326, 334, 321, 322, 323, 320, 330, 0, 85, 86, 300, 301, 89, 90, 91, 92, 93, 94, 95, + 335, 345, 331, 283, 346, 101, 268, 265, 266, 263, 262, 269, 264, 267, 260, 261, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 347, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 0, 0, 0, 0, 0, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, + 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, + 243, 244, 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0 + }; + + int fd = CORE.Input.Keyboard.fd; + if (fd == -1) return; + + struct input_event event = { 0 }; + int keycode = -1; + + // Try to read data from the keyboard and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Button parsing + if (event.type == EV_KEY) + { +#if defined(SUPPORT_SSH_KEYBOARD_RPI) + // Change keyboard mode to events + CORE.Input.Keyboard.evtMode = true; +#endif + // Keyboard button parsing + if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 + { + keycode = keymapUS[event.code & 0xFF]; // The code we get is a scancode so we look up the appropriate keycode + + // Make sure we got a valid keycode + if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) + { + // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt + // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, + // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat + CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; + if (event.value >= 1) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + #if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + #endif + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + + TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); + } + } + } + } +} + +// Input device events reading thread +static void *EventThread(void *arg) +{ + struct input_event event = { 0 }; + InputEventWorker *worker = (InputEventWorker *)arg; + + int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE + bool gestureUpdate = false; // Flag to note gestures require to update + + while (!CORE.Window.shouldClose) + { + // Try to read data from the device and only continue if successful + while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Relative movement parsing + if (event.type == EV_REL) + { + if (event.code == REL_X) + { + CORE.Input.Mouse.currentPosition.x += event.value; + CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x; + + touchAction = 2; // TOUCH_ACTION_MOVE + gestureUpdate = true; + } + + if (event.code == REL_Y) + { + CORE.Input.Mouse.currentPosition.y += event.value; + CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y; + + touchAction = 2; // TOUCH_ACTION_MOVE + gestureUpdate = true; + } + + if (event.code == REL_WHEEL) CORE.Input.Mouse.eventWheelMove.y += event.value; + } + + // Absolute movement parsing + if (event.type == EV_ABS) + { + // Basic movement + if (event.code == ABS_X) + { + CORE.Input.Mouse.currentPosition.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange + CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange + + touchAction = 2; // TOUCH_ACTION_MOVE + gestureUpdate = true; + } + + if (event.code == ABS_Y) + { + CORE.Input.Mouse.currentPosition.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange + CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange + + touchAction = 2; // TOUCH_ACTION_MOVE + gestureUpdate = true; + } + + // Multitouch movement + if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events + + if (event.code == ABS_MT_POSITION_X) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange + } + + if (event.code == ABS_MT_POSITION_Y) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange + } + + if (event.code == ABS_MT_TRACKING_ID) + { + if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) + { + // Touch has ended for this point + CORE.Input.Touch.position[worker->touchSlot].x = -1; + CORE.Input.Touch.position[worker->touchSlot].y = -1; + } + } + + // Touchscreen tap + if (event.code == ABS_PRESSURE) + { + int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; + + if (!event.value && previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; + + touchAction = 0; // TOUCH_ACTION_UP + gestureUpdate = true; + } + + if (event.value && !previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; + + touchAction = 1; // TOUCH_ACTION_DOWN + gestureUpdate = true; + } + } + + } + + // Button parsing + if (event.type == EV_KEY) + { + // Mouse button parsing + if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; + + if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN + else touchAction = 0; // TOUCH_ACTION_UP + gestureUpdate = true; + } + + if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; + if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; + if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; + if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; + if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; + if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; + } + + // Screen confinement + if (!CORE.Input.Mouse.cursorHidden) + { + if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; + 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; + + if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; + 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; + } + + // Update touch point count + CORE.Input.Touch.pointCount = 0; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (CORE.Input.Touch.position[i].x >= 0) CORE.Input.Touch.pointCount++; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_DRM + if (gestureUpdate) + { + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = touchAction; + gestureEvent.pointCount = CORE.Input.Touch.pointCount; + + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + gestureEvent.pointId[i] = i; + gestureEvent.position[i] = CORE.Input.Touch.position[i]; + } + + ProcessGestureEvent(gestureEvent); + } +#endif + } + + WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time + } + + close(worker->fd); + + return NULL; +} + +// Initialize gamepad system +static void InitGamepad(void) +{ + char gamepadDev[128] = { 0 }; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); + + if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0) + { + // NOTE: Only show message for first gamepad + if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); + } + else + { + CORE.Input.Gamepad.ready[i] = true; + + // NOTE: Only create one thread + if (i == 0) + { + int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); + + if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); + else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); + } + } + } +} + +// Process Gamepad (/dev/input/js0) +static void *GamepadThread(void *arg) +{ + #define JS_EVENT_BUTTON 0x01 // Button pressed/released + #define JS_EVENT_AXIS 0x02 // Joystick axis moved + #define JS_EVENT_INIT 0x80 // Initial state of device + + struct js_event { + unsigned int time; // event timestamp in milliseconds + short value; // event value + unsigned char type; // event type + unsigned char number; // event axis/button number + }; + + // Read gamepad event + struct js_event gamepadEvent = { 0 }; + + while (!CORE.Window.shouldClose) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) + { + gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events + + // Process gamepad events by type + if (gamepadEvent.type == JS_EVENT_BUTTON) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) + { + // 1 - button pressed, 0 - button released + CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value; + + if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; + else CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + } + } + else if (gamepadEvent.type == JS_EVENT_AXIS) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_AXIS) + { + // NOTE: Scaling of gamepadEvent.value to get values between -1..1 + CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; + } + } + } + else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time + } + } + + return NULL; +} + +// Search matching DRM mode in connector's mode list +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) +{ + if (NULL == connector) return -1; + if (NULL == mode) return -1; + + // safe bitwise comparison of two modes + #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) + + for (size_t i = 0; i < connector->count_modes; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, + connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; + } + + return -1; + + #undef BINCMP +} + +// Search exactly matching DRM connector mode in connector's list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + 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"); + + if (NULL == connector) return -1; + + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; + + if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; + } + + TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); + return -1; +} + +// Search the nearest matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + 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"); + + if (NULL == connector) return -1; + + int nearestIndex = -1; + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->hdisplay < width) || (mode->vdisplay < height)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); + continue; + } + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); + continue; + } + + if (nearestIndex < 0) + { + nearestIndex = i; + continue; + } + + const int widthDiff = abs(mode->hdisplay - width); + const int heightDiff = abs(mode->vdisplay - height); + const int fpsDiff = abs(mode->vrefresh - fps); + + const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width); + const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height); + const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps); + + if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) { + nearestIndex = i; + } + } + + return nearestIndex; } \ No newline at end of file