Move drm functions to rcore_drm.c
This commit is contained in:
parent
eda153102b
commit
df6260d23b
872
src/rcore.c
872
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 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
|
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)
|
#if defined(SUPPORT_EVENTS_AUTOMATION)
|
||||||
static void LoadAutomationEvents(const char *fileName); // Load automation events from file
|
static void LoadAutomationEvents(const char *fileName); // Load automation events from file
|
||||||
static void ExportAutomationEvents(const char *fileName); // Export recorded automation events into a 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
|
#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 <linux/input.h> 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<N>) 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(x))
|
|
||||||
#define LONG(x) ((x)/BITS_PER_LONG)
|
|
||||||
#define TEST_BIT(array, bit) ((array[LONG(bit)] >> 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<N>"
|
|
||||||
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)
|
#if defined(SUPPORT_EVENTS_AUTOMATION)
|
||||||
// NOTE: Loading happens over AutomationEvent *events
|
// NOTE: Loading happens over AutomationEvent *events
|
||||||
// TODO: This system should probably be redesigned
|
// TODO: This system should probably be redesigned
|
||||||
|
|
|
||||||
865
src/rcore_drm.c
865
src/rcore_drm.c
|
|
@ -5,6 +5,23 @@
|
||||||
#include "rcore.h"
|
#include "rcore.h"
|
||||||
|
|
||||||
static bool InitGraphicsDevice(int width, int height); // Initialize graphics device
|
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
|
// Initialize window and OpenGL context
|
||||||
// NOTE: data parameter could be used to pass any kind of required data to the initialization
|
// 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: Mouse input events polling is done asynchronously in another pthread - EventThread()
|
||||||
// NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
|
// NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
|
||||||
#endif
|
#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 <linux/input.h> 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<N>) 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(x))
|
||||||
|
#define LONG(x) ((x)/BITS_PER_LONG)
|
||||||
|
#define TEST_BIT(array, bit) ((array[LONG(bit)] >> 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<N>"
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user