From 42d9a6ef1db3748e4fb16354b59c028d0ce8e5d1 Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Mon, 23 Mar 2026 00:29:21 +0100 Subject: [PATCH 1/6] linux controller support WIP --- thirteen.h | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 332 insertions(+), 1 deletion(-) diff --git a/thirteen.h b/thirteen.h index 749253e..37f505c 100644 --- a/thirteen.h +++ b/thirteen.h @@ -61,11 +61,27 @@ Chris Cascioli - GetWindowHandle() and warning cleanup #endif #ifdef THIRTEEN_PLATFORM_LINUX + #include + #include + #include + #include + #include + #include + #include #include #include #include #include + + // https://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ch01.html + // https://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/libudev-udev.html + // Forward declare + typedef struct udev udev; + typedef struct udev_enumerate udev_enumerate; + typedef struct udev_list_entry udev_list_entry; + typedef struct udev_device udev_device; + typedef struct udev_monitor udev_monitor; #endif // ========== Common Includes ========== @@ -1727,7 +1743,35 @@ namespace Thirteen void (*glBlitFramebuffer)(GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) = nullptr; GLuint texture = 0; + GLuint framebuffer = 0; + + udev *(*udev_new)(void) = nullptr; + void (*udev_unref)(udev *) = nullptr; + + udev_monitor *(*udev_monitor_new_from_netlink)(udev *, const char *) = nullptr; + udev_monitor *(*udev_monitor_ref)(udev_monitor *) = nullptr; + udev_monitor *(*udev_monitor_unref)(udev_monitor *) = nullptr; + + udev_enumerate *(*udev_enumerate_new)(udev *) = nullptr; + int (*udev_enumerate_add_match_subsystem)(udev_enumerate *, const char *) = nullptr; + int (*udev_enumerate_add_match_property)(udev_enumerate *, const char *, const char *) = nullptr; + int (*udev_enumerate_scan_devices)(udev_enumerate *) = nullptr; + udev_list_entry *(*udev_enumerate_get_list_entry)(udev_enumerate *) = nullptr; + void (*udev_enumerate_unref)(udev_enumerate *) = nullptr; + + udev_list_entry *(*udev_list_entry_get_next)(udev_list_entry *) = nullptr; + const char *(*udev_list_entry_get_name)(udev_list_entry *) = nullptr; + + udev_device *(*udev_device_new_from_syspath)(udev *, const char *) = nullptr; + const char *(*udev_device_get_devnode)(udev_device *) = nullptr; + const char *(*udev_device_get_property_value)(udev_device *, const char *) = nullptr; + void (*udev_device_unref)(udev_device *) = nullptr; + + void * libudevLibrary = nullptr; + + int gamepadFd[1]; + bool foundGamepads = false; bool InitWindow(uint32 width, uint32 height) { @@ -1940,9 +1984,143 @@ namespace Thirteen glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); + +#if 1 + + libudevLibrary = dlopen("libudev.so.1", RTLD_LAZY | RTLD_LOCAL); + if (!libudevLibrary) + { + libudevLibrary = dlopen("libudev.so", RTLD_LAZY | RTLD_LOCAL); + } + + if (!libudevLibrary) + { + return false; + } + + udev_new = (udev*(*)()) dlsym(libudevLibrary, "udev_new"); + udev_unref = (void(*)(udev *)) dlsym(libudevLibrary, "udev_unref"); + + udev_monitor_new_from_netlink = (udev_monitor*(*)(udev *, const char *)) dlsym(libudevLibrary, "udev_monitor_new_from_netlink"); + udev_monitor_ref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_ref"); + udev_monitor_unref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_unref"); + + udev_enumerate_new = (udev_enumerate*(*)(udev *)) dlsym(libudevLibrary, "udev_enumerate_new"); + udev_enumerate_add_match_subsystem = (int(*)(udev_enumerate *, const char *)) dlsym(libudevLibrary, "udev_enumerate_add_match_subsystem"); + + udev_enumerate_add_match_property = (int(*)(udev_enumerate *, const char *, const char *)) dlsym(libudevLibrary, "udev_enumerate_add_match_property"); + udev_enumerate_scan_devices = (int(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_scan_devices"); + udev_enumerate_get_list_entry = (udev_list_entry*(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_get_list_entry"); + udev_enumerate_unref = (void(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_unref"); + + udev_list_entry_get_next = (udev_list_entry*(*)(udev_list_entry *)) dlsym(libudevLibrary, "udev_list_entry_get_next"); + udev_list_entry_get_name= (const char*(*)(udev_list_entry *)) dlsym(libudevLibrary, "udev_list_entry_get_name"); + + udev_device_new_from_syspath= (udev_device*(*)(udev *, const char *)) dlsym(libudevLibrary, "udev_device_new_from_syspath"); + udev_device_get_devnode = (const char*(*)(udev_device *)) dlsym(libudevLibrary, "udev_device_get_devnode"); + udev_device_get_property_value = (const char*(*)(udev_device *, const char *)) dlsym(libudevLibrary, "udev_device_get_property_value"); + + udev_device_unref = (void(*)(udev_device *)) dlsym(libudevLibrary, "udev_device_unref"); + + // = (int(*)()) dlsym(libudevLibrary, ""); + + udev* udevCtx = udev_new(); + if (udevCtx) + { + // TODO: Monitor hotplugged input + //udev_monitor *udevMonitor = udev_monitor_new_from_netlink(udevCtx, "input"); + //udev_monitor_enable_receiving(udevMonitor); + + // Initial scan + udev_enumerate* udevEnumerator = udev_enumerate_new(udevCtx); + udev_device *potentialGamepadDevice = nullptr; + + if (udevEnumerator) + { + udev_enumerate_add_match_subsystem(udevEnumerator, "input"); + + udev_enumerate_add_match_property(udevEnumerator, "ID_INPUT_JOYSTICK", "1"); + + udev_enumerate_scan_devices(udevEnumerator); + udev_list_entry* deviceList = udev_enumerate_get_list_entry(udevEnumerator); + + udev_list_entry* element = deviceList; + int i = 1; + for (; element; element = udev_list_entry_get_next(element)) + { + printf("\ni = %d\n", i++); + const char* devicePath = udev_list_entry_get_name(element); + udev_device *device = udev_device_new_from_syspath(udevCtx, devicePath); + const char* devNode = udev_device_get_devnode(device); + + if (device) + { + + if (devicePath && devNode) + { + printf("Device information:\n"); + printf("devicePath = %s\ndevNode = %s\n", devicePath, devNode); + + int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); + if (possibleGamepadFd) + { + // User needs to be in the "input" group for this to work + // For more info on ioctl and the EVIOC macros + // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes + + // Query for info and capabilities + struct input_id devId; + ioctl(possibleGamepadFd, EVIOCGID, &devId); + char name[256] = {0}; + ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); + + printf("Device: %s (vendor=%04x product=%04x)\n", name, devId.vendor, devId.product); + + // Interrogate the potential gamepad + + // We're working with bit arrays when using EVIOCGBIT + + unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; + if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) + { + printf("hm..\n"); + unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; + bool isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); + if (isGamepad) + { + foundGamepads = true; + gamepadFd[0] = possibleGamepadFd; + udev_device_unref(device); + break; + } + } + + close(possibleGamepadFd); + } + } + + + + potentialGamepadDevice = device; + udev_device_unref(device); // Will leak memory otherwise! + } + } + + } + + udev_enumerate_unref(udevEnumerator); + + } +#endif + + if (foundGamepads) + { + printf("Found at least one gamepad!\n"); + } + return true; } - + int remapMouseButton(int x11Button) { switch (x11Button) @@ -2004,6 +2182,7 @@ namespace Thirteen break; } } + } void SetTitle(const char* title) @@ -2060,11 +2239,163 @@ namespace Thirteen bool Init(PlatformLinuxX11GL * platform, uint32, uint32) { this->platform = platform; + return true; } bool Render(const uint8* pixels, uint32, uint32, bool) { + for (int controllerIndex = 0; platform->foundGamepads && (controllerIndex < 1); ++controllerIndex) + { + // Future reference: evdev is event-based, unlike the state-based win32 XInput. Therefore, do no zero current state. + //controllers[controllerIndex] = ControllerState{}; + + ControllerState* controller = &controllers[controllerIndex]; + + struct input_event events[32]; + + struct pollfd pollFd; + pollFd.fd = platform->gamepadFd[controllerIndex]; + pollFd.events = POLLIN; + pollFd.revents = 0; + + if (poll(&pollFd, 1, 0) > 0) + { + int bytesRead = read(platform->gamepadFd[controllerIndex], &events, sizeof(events)); + if(bytesRead > 0) + { + int eventCount = bytesRead / sizeof(struct input_event); + for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) + { + struct input_event* event = &events[eventIndex]; + if (event->type == EV_KEY) + { + bool pressed = event->value == 1; + bool released = event->value == 0; + bool autoRepeat = event->value == 2; + + if(event->code == BTN_SOUTH) + pressed ? controller->buttons |= ControllerButton::A : controller->buttons &= ~ControllerButton::A; + else if(event->code == BTN_EAST) + pressed ? controller->buttons |= ControllerButton::B : controller->buttons &= ~ControllerButton::B; + else if(event->code == BTN_WEST) + pressed ? controller->buttons |= ControllerButton::X : controller->buttons &= ~ControllerButton::X; + else if(event->code == BTN_NORTH) + pressed ? controller->buttons |= ControllerButton::Y : controller->buttons &= ~ControllerButton::Y; + + else if(event->code == BTN_DPAD_RIGHT) + pressed ? controller->buttons |= ControllerButton::DPadRight : controller->buttons &= ~ControllerButton::DPadRight; + else if(event->code == BTN_DPAD_DOWN) + pressed ? controller->buttons |= ControllerButton::DPadDown : controller->buttons &= ~ControllerButton::DPadDown; + else if(event->code == BTN_DPAD_LEFT) + pressed ? controller->buttons |= ControllerButton::DPadLeft : controller->buttons &= ~ControllerButton::DPadLeft; + else if(event->code == BTN_DPAD_UP) + pressed ? controller->buttons |= ControllerButton::DPadUp : controller->buttons &= ~ControllerButton::DPadUp; + + else if(event->code == BTN_THUMBL) + pressed ? controller->buttons |= ControllerButton::LeftThumb : controller->buttons &= ~ControllerButton::LeftThumb; + else if(event->code == BTN_THUMBR) + pressed ? controller->buttons |= ControllerButton::RightThumb : controller->buttons &= ~ControllerButton::RightThumb; + + } + else if (event->type == EV_ABS) + { + // TODO: Read axis values + + // For some controllers, DPAD will come through ABS_HAT codes + if (event->code == ABS_HAT0X) + { + if (event->value == 1) + controller->buttons |= ControllerButton::DPadRight; + else if (event->value == -1) + controller->buttons |= ControllerButton::DPadLeft; + else if (event->value == 0) // left/right was released + controllers->buttons &= ~(ControllerButton::DPadRight | ControllerButton::DPadLeft); + } + else if (event->code == ABS_HAT0Y) + { + if (event->value == 1) + controller->buttons |= ControllerButton::DPadDown; + else if (event->value == -1) + controller->buttons |= ControllerButton::DPadUp; + else if (event->value == 0) // up/down was released + controllers->buttons &= ~(ControllerButton::DPadUp | ControllerButton::DPadDown); + } + + if (event->code == ABS_X) + { + // X values of the left stick + struct input_absinfo absInfo; + ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); + //printf("absinfo of ABS_X:\n"); + //printf("value = %d\nmin = %d\nmax = %d\nfuzz = %d\nflat = %d\nresolution = %d\n", absInfo.value, absInfo.minimum, absInfo.maximum, absInfo.fuzz, absInfo.flat, absInfo.resolution); + //printf("val = %d\n", event->value); + + // Map an arbitrary range [absInfo.minimum, absInfo.maximum] to [-1, 1] + // by first normalizing to [0, 1] then scale it to [-1, 1] + + controller->leftThumbX = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->leftThumbX = controller->leftThumbX*2 - 1; + + // deadzone + int centerX = (absInfo.maximum + absInfo.minimum) / 2; + + } + else if (event->code == ABS_Y) + { + // Y values of the left stick + struct input_absinfo absInfo; + ioctl(pollFd.fd, EVIOCGABS(ABS_Y), &absInfo); + + controller->leftThumbY = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->leftThumbY = controller->leftThumbY*2 - 1; + + int centerY = (absInfo.maximum + absInfo.minimum) / 2; + + } + else if (event->code == ABS_RX) + { + // X values of the left stick + struct input_absinfo absInfo; + ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); + //printf("absinfo of ABS_X:\n"); + //printf("value = %d\nmin = %d\nmax = %d\nfuzz = %d\nflat = %d\nresolution = %d\n", absInfo.value, absInfo.minimum, absInfo.maximum, absInfo.fuzz, absInfo.flat, absInfo.resolution); + //printf("val = %d\n", event->value); + + // Map an arbitrary range [absInfo.minimum, absInfo.maximum] to [-1, 1] + // by first normalizing to [0, 1] then scale it to [-1, 1] + + controller->rightThumbX = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->rightThumbX = controller->rightThumbX*2 - 1; + // deadzone + int centerX = (absInfo.maximum + absInfo.minimum) / 2; + + } + else if (event->code == ABS_RY) + { + // Y values of the left stick + struct input_absinfo absInfo; + ioctl(pollFd.fd, EVIOCGABS(ABS_Y), &absInfo); + + controller->rightThumbY = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->rightThumbY = controller->rightThumbY*2 - 1; + + int centerY = (absInfo.maximum + absInfo.minimum) / 2; + + } + } + else if (event->type == EV_SYN) + { + if (event->code == SYN_DROPPED) + { + // TODO: Handle missed event + } + } + } + } + } + } + return platform->DoRender(pixels); } From 98b0df6871c56622a96a48da0722f684395f861a Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Mon, 23 Mar 2026 11:43:06 +0100 Subject: [PATCH 2/6] more linux controller support WIP --- thirteen.h | 83 ++++++++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/thirteen.h b/thirteen.h index 37f505c..4263904 100644 --- a/thirteen.h +++ b/thirteen.h @@ -1984,9 +1984,6 @@ namespace Thirteen glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); - -#if 1 - libudevLibrary = dlopen("libudev.so.1", RTLD_LAZY | RTLD_LOCAL); if (!libudevLibrary) { @@ -2045,10 +2042,9 @@ namespace Thirteen udev_list_entry* deviceList = udev_enumerate_get_list_entry(udevEnumerator); udev_list_entry* element = deviceList; - int i = 1; + for (; element; element = udev_list_entry_get_next(element)) { - printf("\ni = %d\n", i++); const char* devicePath = udev_list_entry_get_name(element); udev_device *device = udev_device_new_from_syspath(udevCtx, devicePath); const char* devNode = udev_device_get_devnode(device); @@ -2058,14 +2054,11 @@ namespace Thirteen if (devicePath && devNode) { - printf("Device information:\n"); - printf("devicePath = %s\ndevNode = %s\n", devicePath, devNode); - int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); if (possibleGamepadFd) { - // User needs to be in the "input" group for this to work - // For more info on ioctl and the EVIOC macros + // User needs to be in the "input" group for this to work. + // For more info on ioctl and the EVIOC macros: // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes // Query for info and capabilities @@ -2074,16 +2067,11 @@ namespace Thirteen char name[256] = {0}; ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); - printf("Device: %s (vendor=%04x product=%04x)\n", name, devId.vendor, devId.product); - // Interrogate the potential gamepad - - // We're working with bit arrays when using EVIOCGBIT - + // NOTE: We're working with bit arrays when using EVIOCGBIT unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) { - printf("hm..\n"); unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; bool isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); if (isGamepad) @@ -2105,13 +2093,10 @@ namespace Thirteen udev_device_unref(device); // Will leak memory otherwise! } } - } - + udev_enumerate_unref(udevEnumerator); - } -#endif if (foundGamepads) { @@ -2247,7 +2232,7 @@ namespace Thirteen { for (int controllerIndex = 0; platform->foundGamepads && (controllerIndex < 1); ++controllerIndex) { - // Future reference: evdev is event-based, unlike the state-based win32 XInput. Therefore, do no zero current state. + // Future reference: evdev emits event, unlike the state-based win32 XInput. Therefore, do no zero the current state. //controllers[controllerIndex] = ControllerState{}; ControllerState* controller = &controllers[controllerIndex]; @@ -2271,36 +2256,45 @@ namespace Thirteen if (event->type == EV_KEY) { bool pressed = event->value == 1; - bool released = event->value == 0; - bool autoRepeat = event->value == 2; + //bool released = event->value == 0; + //bool autoRepeat = event->value == 2; - if(event->code == BTN_SOUTH) + if (event->code == BTN_SOUTH) pressed ? controller->buttons |= ControllerButton::A : controller->buttons &= ~ControllerButton::A; - else if(event->code == BTN_EAST) + else if (event->code == BTN_EAST) pressed ? controller->buttons |= ControllerButton::B : controller->buttons &= ~ControllerButton::B; - else if(event->code == BTN_WEST) + else if (event->code == BTN_WEST) pressed ? controller->buttons |= ControllerButton::X : controller->buttons &= ~ControllerButton::X; - else if(event->code == BTN_NORTH) + else if (event->code == BTN_NORTH) pressed ? controller->buttons |= ControllerButton::Y : controller->buttons &= ~ControllerButton::Y; - else if(event->code == BTN_DPAD_RIGHT) + else if (event->code == BTN_DPAD_RIGHT) pressed ? controller->buttons |= ControllerButton::DPadRight : controller->buttons &= ~ControllerButton::DPadRight; - else if(event->code == BTN_DPAD_DOWN) + else if (event->code == BTN_DPAD_DOWN) pressed ? controller->buttons |= ControllerButton::DPadDown : controller->buttons &= ~ControllerButton::DPadDown; - else if(event->code == BTN_DPAD_LEFT) + else if (event->code == BTN_DPAD_LEFT) pressed ? controller->buttons |= ControllerButton::DPadLeft : controller->buttons &= ~ControllerButton::DPadLeft; - else if(event->code == BTN_DPAD_UP) + else if (event->code == BTN_DPAD_UP) pressed ? controller->buttons |= ControllerButton::DPadUp : controller->buttons &= ~ControllerButton::DPadUp; - else if(event->code == BTN_THUMBL) + else if (event->code == BTN_THUMBL) pressed ? controller->buttons |= ControllerButton::LeftThumb : controller->buttons &= ~ControllerButton::LeftThumb; - else if(event->code == BTN_THUMBR) + else if (event->code == BTN_THUMBR) pressed ? controller->buttons |= ControllerButton::RightThumb : controller->buttons &= ~ControllerButton::RightThumb; + else if (event->code == BTN_TL) + pressed ? controller->buttons |= ControllerButton::LeftShoulder : controller->buttons &= ~ControllerButton::LeftShoulder; + else if (event->code == BTN_TR) + pressed ? controller->buttons |= ControllerButton::RightShoulder : controller->buttons &= ~ControllerButton::RightShoulder; + + else if (event->code == BTN_SELECT) + pressed ? controller->buttons |= ControllerButton::Back : controller->buttons &= ~ControllerButton::Back; + else if (event->code == BTN_START) + pressed ? controller->buttons |= ControllerButton::Start : controller->buttons &= ~ControllerButton::Start; + } else if (event->type == EV_ABS) { - // TODO: Read axis values // For some controllers, DPAD will come through ABS_HAT codes if (event->code == ABS_HAT0X) @@ -2327,9 +2321,6 @@ namespace Thirteen // X values of the left stick struct input_absinfo absInfo; ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); - //printf("absinfo of ABS_X:\n"); - //printf("value = %d\nmin = %d\nmax = %d\nfuzz = %d\nflat = %d\nresolution = %d\n", absInfo.value, absInfo.minimum, absInfo.maximum, absInfo.fuzz, absInfo.flat, absInfo.resolution); - //printf("val = %d\n", event->value); // Map an arbitrary range [absInfo.minimum, absInfo.maximum] to [-1, 1] // by first normalizing to [0, 1] then scale it to [-1, 1] @@ -2338,7 +2329,7 @@ namespace Thirteen controller->leftThumbX = controller->leftThumbX*2 - 1; // deadzone - int centerX = (absInfo.maximum + absInfo.minimum) / 2; + //int centerX = (absInfo.maximum + absInfo.minimum) / 2; } else if (event->code == ABS_Y) @@ -2355,32 +2346,26 @@ namespace Thirteen } else if (event->code == ABS_RX) { - // X values of the left stick + // X values of the right stick struct input_absinfo absInfo; ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); - //printf("absinfo of ABS_X:\n"); - //printf("value = %d\nmin = %d\nmax = %d\nfuzz = %d\nflat = %d\nresolution = %d\n", absInfo.value, absInfo.minimum, absInfo.maximum, absInfo.fuzz, absInfo.flat, absInfo.resolution); - //printf("val = %d\n", event->value); - - // Map an arbitrary range [absInfo.minimum, absInfo.maximum] to [-1, 1] - // by first normalizing to [0, 1] then scale it to [-1, 1] controller->rightThumbX = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); controller->rightThumbX = controller->rightThumbX*2 - 1; - // deadzone - int centerX = (absInfo.maximum + absInfo.minimum) / 2; + + // int centerX = (absInfo.maximum + absInfo.minimum) / 2; } else if (event->code == ABS_RY) { - // Y values of the left stick + // Y values of the right stick struct input_absinfo absInfo; ioctl(pollFd.fd, EVIOCGABS(ABS_Y), &absInfo); controller->rightThumbY = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); controller->rightThumbY = controller->rightThumbY*2 - 1; - int centerY = (absInfo.maximum + absInfo.minimum) / 2; + //int centerY = (absInfo.maximum + absInfo.minimum) / 2; } } From 99f5c2b1e778f5c2212d0f6aaae497f9d18409d1 Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Mon, 23 Mar 2026 21:43:42 +0100 Subject: [PATCH 3/6] First pass of Linux gamepad support done. Also changed the ControllerTest example to iterate through four controllers --- Examples/ControllerTest/main.cpp | 76 ++++---- thirteen.h | 321 ++++++++++++++++++++++--------- 2 files changed, 272 insertions(+), 125 deletions(-) diff --git a/Examples/ControllerTest/main.cpp b/Examples/ControllerTest/main.cpp index 7493ceb..02be6a1 100644 --- a/Examples/ControllerTest/main.cpp +++ b/Examples/ControllerTest/main.cpp @@ -49,47 +49,51 @@ int main(int argc, char** argv) { Thirteen::ControllerButton::Y, "Y" } }; - for (const ButtonInfo& buttonInfo : buttons) + for(int controllerIndex = 0; controllerIndex < 4; controllerIndex++) { - const bool currentState = Thirteen::GetControllerButton(0, buttonInfo.button); - const bool previousState = Thirteen::GetControllerButtonLastFrame(0, buttonInfo.button); - if (currentState != previousState) + for (const ButtonInfo& buttonInfo : buttons) { - if (currentState) - printf("%s pressed\n", buttonInfo.name); - else - printf("%s released\n", buttonInfo.name); + const bool currentState = Thirteen::GetControllerButton(controllerIndex, buttonInfo.button); + const bool previousState = Thirteen::GetControllerButtonLastFrame(controllerIndex, buttonInfo.button); + + if (currentState != previousState) + { + if (currentState) + printf("[%d]: %s pressed\n", controllerIndex, buttonInfo.name); + else + printf("[%d]: %s released\n", controllerIndex, buttonInfo.name); + } } - } - const float leftTrigger = Thirteen::GetControllerTrigger(0, true); - const float leftTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, true); - if (fabsf(leftTrigger - leftTriggerLastFrame) > c_thumbStickEpsilon) - printf("LeftTrigger: %.3f\n", leftTrigger); - - const float rightTrigger = Thirteen::GetControllerTrigger(0, false); - const float rightTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, false); - if (fabsf(rightTrigger - rightTriggerLastFrame) > c_thumbStickEpsilon) - printf("RightTrigger: %.3f\n", rightTrigger); - - float leftThumbX = 0.0f; - float leftThumbY = 0.0f; - float leftThumbXLastFrame = 0.0f; - float leftThumbYLastFrame = 0.0f; - Thirteen::GetControllerThumbstick(0, true, leftThumbX, leftThumbY); - Thirteen::GetControllerThumbstickLastFrame(0, true, leftThumbXLastFrame, leftThumbYLastFrame); - if (fabsf(leftThumbX - leftThumbXLastFrame) > c_thumbStickEpsilon || fabsf(leftThumbY - leftThumbYLastFrame) > c_thumbStickEpsilon) - printf("LeftThumb: x=%.3f, y=%.3f\n", leftThumbX, leftThumbY); - - float rightThumbX = 0.0f; - float rightThumbY = 0.0f; - float rightThumbXLastFrame = 0.0f; - float rightThumbYLastFrame = 0.0f; - Thirteen::GetControllerThumbstick(0, false, rightThumbX, rightThumbY); - Thirteen::GetControllerThumbstickLastFrame(0, false, rightThumbXLastFrame, rightThumbYLastFrame); - if (fabsf(rightThumbX - rightThumbXLastFrame) > c_thumbStickEpsilon || fabsf(rightThumbY - rightThumbYLastFrame) > c_thumbStickEpsilon) - printf("RightThumb: x=%.3f, y=%.3f\n", rightThumbX, rightThumbY); + const float leftTrigger = Thirteen::GetControllerTrigger(controllerIndex, true); + const float leftTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, true); + if (fabsf(leftTrigger - leftTriggerLastFrame) > c_thumbStickEpsilon) + printf("[%d]: LeftTrigger: %.3f\n", controllerIndex, leftTrigger); + + const float rightTrigger = Thirteen::GetControllerTrigger(controllerIndex, false); + const float rightTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(controllerIndex, false); + if (fabsf(rightTrigger - rightTriggerLastFrame) > c_thumbStickEpsilon) + printf("[%d]: RightTrigger: %.3f\n", controllerIndex, rightTrigger); + + float leftThumbX = 0.0f; + float leftThumbY = 0.0f; + float leftThumbXLastFrame = 0.0f; + float leftThumbYLastFrame = 0.0f; + Thirteen::GetControllerThumbstick(controllerIndex, true, leftThumbX, leftThumbY); + Thirteen::GetControllerThumbstickLastFrame(controllerIndex, true, leftThumbXLastFrame, leftThumbYLastFrame); + if (fabsf(leftThumbX - leftThumbXLastFrame) > c_thumbStickEpsilon || fabsf(leftThumbY - leftThumbYLastFrame) > c_thumbStickEpsilon) + printf("[%d]: LeftThumb: x=%.3f, y=%.3f\n", controllerIndex, leftThumbX, leftThumbY); + + float rightThumbX = 0.0f; + float rightThumbY = 0.0f; + float rightThumbXLastFrame = 0.0f; + float rightThumbYLastFrame = 0.0f; + Thirteen::GetControllerThumbstick(controllerIndex, false, rightThumbX, rightThumbY); + Thirteen::GetControllerThumbstickLastFrame(controllerIndex, false, rightThumbXLastFrame, rightThumbYLastFrame); + if (fabsf(rightThumbX - rightThumbXLastFrame) > c_thumbStickEpsilon || fabsf(rightThumbY - rightThumbYLastFrame) > c_thumbStickEpsilon) + printf("[%d]: RightThumb: x=%.3f, y=%.3f\n", controllerIndex, rightThumbX, rightThumbY); + } } while (Thirteen::Render() && !Thirteen::GetKey(VK_ESCAPE)); diff --git a/thirteen.h b/thirteen.h index 4263904..6b1efc4 100644 --- a/thirteen.h +++ b/thirteen.h @@ -1743,13 +1743,16 @@ namespace Thirteen void (*glBlitFramebuffer)(GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) = nullptr; GLuint texture = 0; - GLuint framebuffer = 0; udev *(*udev_new)(void) = nullptr; void (*udev_unref)(udev *) = nullptr; udev_monitor *(*udev_monitor_new_from_netlink)(udev *, const char *) = nullptr; + int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *, const char *, const char *) = nullptr; + int (*udev_monitor_enable_receiving)(struct udev_monitor *) = nullptr; + int (*udev_monitor_get_fd)(struct udev_monitor *) = nullptr; + udev_device *(*udev_monitor_receive_device)(struct udev_monitor *) = nullptr; udev_monitor *(*udev_monitor_ref)(udev_monitor *) = nullptr; udev_monitor *(*udev_monitor_unref)(udev_monitor *) = nullptr; @@ -1769,9 +1772,18 @@ namespace Thirteen void (*udev_device_unref)(udev_device *) = nullptr; void * libudevLibrary = nullptr; + - int gamepadFd[1]; - bool foundGamepads = false; + int hotplugFd = -1; + udev_monitor* udevMonitor = nullptr; + + // Bookkeeping + int gamepadFd[4] = {-1, -1, -1, -1}; + static const int maxDevNodeLength = 256; + char gamepadDevNode[4][maxDevNodeLength] = {0}; + int gamepadCount = 0; + + bool foundOneGamepad = false; bool InitWindow(uint32 width, uint32 height) { @@ -1999,6 +2011,10 @@ namespace Thirteen udev_unref = (void(*)(udev *)) dlsym(libudevLibrary, "udev_unref"); udev_monitor_new_from_netlink = (udev_monitor*(*)(udev *, const char *)) dlsym(libudevLibrary, "udev_monitor_new_from_netlink"); + udev_monitor_filter_add_match_subsystem_devtype = (int(*)(struct udev_monitor *, const char *, const char *)) dlsym(libudevLibrary, "udev_monitor_filter_add_match_subsystem_devtype"); + udev_monitor_enable_receiving = (int(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_enable_receiving"); + udev_monitor_receive_device = (udev_device*(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_receive_device"); + udev_monitor_get_fd = (int(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_get_fd"); udev_monitor_ref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_ref"); udev_monitor_unref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_unref"); @@ -2025,17 +2041,21 @@ namespace Thirteen if (udevCtx) { // TODO: Monitor hotplugged input - //udev_monitor *udevMonitor = udev_monitor_new_from_netlink(udevCtx, "input"); - //udev_monitor_enable_receiving(udevMonitor); + udevMonitor = udev_monitor_new_from_netlink(udevCtx, "udev"); + if (udevMonitor) + { + udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "input", NULL); + udev_monitor_enable_receiving(udevMonitor); + + hotplugFd = udev_monitor_get_fd(udevMonitor); + } // Initial scan - udev_enumerate* udevEnumerator = udev_enumerate_new(udevCtx); - udev_device *potentialGamepadDevice = nullptr; + udev_enumerate *udevEnumerator = udev_enumerate_new(udevCtx); if (udevEnumerator) { udev_enumerate_add_match_subsystem(udevEnumerator, "input"); - udev_enumerate_add_match_property(udevEnumerator, "ID_INPUT_JOYSTICK", "1"); udev_enumerate_scan_devices(udevEnumerator); @@ -2045,51 +2065,64 @@ namespace Thirteen for (; element; element = udev_list_entry_get_next(element)) { - const char* devicePath = udev_list_entry_get_name(element); - udev_device *device = udev_device_new_from_syspath(udevCtx, devicePath); - const char* devNode = udev_device_get_devnode(device); + if (gamepadCount >= 4) break; + const char* devicePath = udev_list_entry_get_name(element); + udev_device* device = udev_device_new_from_syspath(udevCtx, devicePath); if (device) { - - if (devicePath && devNode) + const char* devNode = udev_device_get_devnode(device); + if (devNode) //&& devicePath { - int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); - if (possibleGamepadFd) - { - // User needs to be in the "input" group for this to work. - // For more info on ioctl and the EVIOC macros: - // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes + printf("devNode = %s\n", devNode); + int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); + if (possibleGamepadFd >= 0) + { + // User needs to be in the "input" group for this to work. + // For more info on ioctl and the EVIOC macros: + // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes + + // Query for info and capabilities + struct input_id devId; + ioctl(possibleGamepadFd, EVIOCGID, &devId); + char name[256] = {0}; + ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); + printf("(%s) found (fd=%d).\n", name, possibleGamepadFd); - // Query for info and capabilities - struct input_id devId; - ioctl(possibleGamepadFd, EVIOCGID, &devId); - char name[256] = {0}; - ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); - - // Interrogate the potential gamepad - // NOTE: We're working with bit arrays when using EVIOCGBIT - unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; - if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) - { - unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; - bool isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); - if (isGamepad) - { - foundGamepads = true; - gamepadFd[0] = possibleGamepadFd; - udev_device_unref(device); - break; - } - } - - close(possibleGamepadFd); - } + // Interrogate the potential gamepad by checking if the device has the BTN_GAMEPAD button + // NOTE: We're working with bit arrays when using EVIOCGBIT + unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; + bool isGamepad = false; + + if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) + { + unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; + isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); + if (isGamepad) + { + // Check for empty slot + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + if (gamepadFd[gamepadIndex] == -1 && strlen(devNode) < maxDevNodeLength) + { + foundOneGamepad = true; + gamepadFd[gamepadIndex] = possibleGamepadFd; + memcpy(gamepadDevNode[gamepadIndex], devNode, strlen(devNode)); + gamepadCount++; + printf("Initial scan of gamepad (%s) found (fd=%d) with dev node %s\n", name, possibleGamepadFd, gamepadDevNode[gamepadIndex]); + break; + } + } + + // No empty slot available + } + } + + if (!isGamepad) + close(possibleGamepadFd); + } } - - - - potentialGamepadDevice = device; + udev_device_unref(device); // Will leak memory otherwise! } } @@ -2098,9 +2131,14 @@ namespace Thirteen udev_enumerate_unref(udevEnumerator); } - if (foundGamepads) + if (foundOneGamepad) { - printf("Found at least one gamepad!\n"); + printf("Found %d gamepad%s\n", gamepadCount, (gamepadCount > 1) ? "s!" : "!"); + printf("After the initial scan:\n"); + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + printf("fd = %d, devnode = %s\n", gamepadFd[gamepadIndex], gamepadDevNode[gamepadIndex]); + } } return true; @@ -2132,43 +2170,141 @@ namespace Thirteen } void PumpMessages() - { - XEvent event; + { + XEvent event; + + while (XPending(x11Display)) { + XNextEvent(x11Display, &event); + switch (event.type) + { + case KeyPress: + if (int keycode = remapKeyEvent(event.xkey); unsigned(keycode) < 256) + { + keys[keycode] = true; + } + break; + case KeyRelease: + if (int keycode = remapKeyEvent(event.xkey); unsigned(keycode) < 256) + { + keys[keycode] = false; + } + break; + case ButtonPress: + mouseButtons[remapMouseButton(event.xbutton.button)] = true; + break; + case ButtonRelease: + mouseButtons[remapMouseButton(event.xbutton.button)] = false; + break; + case MotionNotify: + mouseX = event.xmotion.x; + mouseY = event.xmotion.y; + break; + case ClientMessage: + if ((Atom)event.xclient.data.l[0] == closeWindowAtom) + shouldQuit = true; + break; + } + } + + // Check if any new gamepads was hotplugged, or if we lost connection + if (udevMonitor && hotplugFd) + { + struct pollfd pollFd; + pollFd.fd = hotplugFd; + pollFd.events = POLLIN; + pollFd.revents = 0; + + if (poll(&pollFd, 1, 0) > 0) + { + struct udev_device* device = udev_monitor_receive_device(udevMonitor); + + if (device) + { + const char* propValue = udev_device_get_property_value(device, "ID_INPUT_JOYSTICK"); + if (propValue && strcmp(propValue, "1") == 0) + { + const char* devNode = udev_device_get_devnode(device); + if (devNode) + { + printf("possible hotplug at %s\n", devNode); + int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); + if (possibleGamepadFd > 0 && gamepadCount < 4) + { + printf("possible hotplug fd is %d\n", possibleGamepadFd); + // Query for info and capabilities + struct input_id devId; + ioctl(possibleGamepadFd, EVIOCGID, &devId); + + char name[256] = {0}; + ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); + + // Interrogate the potential gamepad + // NOTE: We're working with bit arrays when using EVIOCGBIT + unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; + bool isGamepad = false; + if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) + { + unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; + isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); + if (isGamepad) + { + + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + if (gamepadFd[gamepadIndex] == -1 && strlen(devNode) < maxDevNodeLength) + { + gamepadFd[gamepadIndex] = possibleGamepadFd; + memcpy(gamepadDevNode[gamepadIndex], devNode, strlen(devNode)); + gamepadCount++; + printf("Hotplugged a gamepad?\n"); + foundOneGamepad = true; + break; + } + } + } + } + + if (!isGamepad) + close(possibleGamepadFd); + } + else + { + // This might be something that got unplugged. + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + if (gamepadFd[gamepadIndex] != -1) + { + bool isTheUnpluggedGamepad = strcmp(gamepadDevNode[gamepadIndex], devNode) == 0; + if (isTheUnpluggedGamepad) + { + close(gamepadFd[gamepadIndex]); + + // Reset this slot + gamepadFd[gamepadIndex] = -1; + memset(gamepadDevNode[gamepadIndex], 0, maxDevNodeLength); + + gamepadCount--; + printf("Hotunplugged a gamepad?\n"); + break; + } + } + } + } + + printf("After udev monitor:\n"); + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + printf("fd = %d, devnode = %s\n", gamepadFd[gamepadIndex], gamepadDevNode[gamepadIndex]); + } + } + } + + udev_device_unref(device); + } + } + } + } - while (XPending(x11Display)) { - XNextEvent(x11Display, &event); - switch (event.type) - { - case KeyPress: - if (int keycode = remapKeyEvent(event.xkey); unsigned(keycode) < 256) - { - keys[keycode] = true; - } - break; - case KeyRelease: - if (int keycode = remapKeyEvent(event.xkey); unsigned(keycode) < 256) - { - keys[keycode] = false; - } - break; - case ButtonPress: - mouseButtons[remapMouseButton(event.xbutton.button)] = true; - break; - case ButtonRelease: - mouseButtons[remapMouseButton(event.xbutton.button)] = false; - break; - case MotionNotify: - mouseX = event.xmotion.x; - mouseY = event.xmotion.y; - break; - case ClientMessage: - if ((Atom)event.xclient.data.l[0] == closeWindowAtom) - shouldQuit = true; - break; - } - } - - } void SetTitle(const char* title) { @@ -2230,11 +2366,12 @@ namespace Thirteen bool Render(const uint8* pixels, uint32, uint32, bool) { - for (int controllerIndex = 0; platform->foundGamepads && (controllerIndex < 1); ++controllerIndex) + for (int controllerIndex = 0; platform->foundOneGamepad && (controllerIndex < 4); ++controllerIndex) { // Future reference: evdev emits event, unlike the state-based win32 XInput. Therefore, do no zero the current state. //controllers[controllerIndex] = ControllerState{}; + if (platform->gamepadFd[controllerIndex] == -1) continue; ControllerState* controller = &controllers[controllerIndex]; struct input_event events[32]; @@ -2244,14 +2381,19 @@ namespace Thirteen pollFd.events = POLLIN; pollFd.revents = 0; - if (poll(&pollFd, 1, 0) > 0) + int pollResult = poll(&pollFd, 1, 0); + if (pollResult > 0) { int bytesRead = read(platform->gamepadFd[controllerIndex], &events, sizeof(events)); + //printf("slot %d (fd=%d): poll hit, read %d bytes\n", controllerIndex, pollFd.fd, bytesRead); + if(bytesRead > 0) { + //printf("bytesRead > 0 (fd=%d)\n", pollFd.fd); int eventCount = bytesRead / sizeof(struct input_event); for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) { + //printf(" event type=%d code=%d value=%d\n", events[eventIndex].type, events[eventIndex].code, events[eventIndex].value); struct input_event* event = &events[eventIndex]; if (event->type == EV_KEY) { @@ -2304,7 +2446,7 @@ namespace Thirteen else if (event->value == -1) controller->buttons |= ControllerButton::DPadLeft; else if (event->value == 0) // left/right was released - controllers->buttons &= ~(ControllerButton::DPadRight | ControllerButton::DPadLeft); + controller->buttons &= ~(ControllerButton::DPadRight | ControllerButton::DPadLeft); } else if (event->code == ABS_HAT0Y) { @@ -2313,7 +2455,7 @@ namespace Thirteen else if (event->value == -1) controller->buttons |= ControllerButton::DPadUp; else if (event->value == 0) // up/down was released - controllers->buttons &= ~(ControllerButton::DPadUp | ControllerButton::DPadDown); + controller->buttons &= ~(ControllerButton::DPadUp | ControllerButton::DPadDown); } if (event->code == ABS_X) @@ -2374,6 +2516,7 @@ namespace Thirteen if (event->code == SYN_DROPPED) { // TODO: Handle missed event + printf("SYN_DROPPED\n"); } } } From 543025e24ab682930c3b223e1005792f77a351e8 Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Tue, 24 Mar 2026 11:34:00 +0100 Subject: [PATCH 4/6] Second pass of basic Linux gamepad support --- Examples/ControllerTest/main.cpp | 2 +- thirteen.h | 164 +++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 55 deletions(-) diff --git a/Examples/ControllerTest/main.cpp b/Examples/ControllerTest/main.cpp index 02be6a1..3f240d8 100644 --- a/Examples/ControllerTest/main.cpp +++ b/Examples/ControllerTest/main.cpp @@ -67,7 +67,7 @@ int main(int argc, char** argv) } const float leftTrigger = Thirteen::GetControllerTrigger(controllerIndex, true); - const float leftTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, true); + const float leftTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(controllerIndex, true); if (fabsf(leftTrigger - leftTriggerLastFrame) > c_thumbStickEpsilon) printf("[%d]: LeftTrigger: %.3f\n", controllerIndex, leftTrigger); diff --git a/thirteen.h b/thirteen.h index 6b1efc4..404cb66 100644 --- a/thirteen.h +++ b/thirteen.h @@ -1707,6 +1707,54 @@ namespace Thirteen struct PlatformLinuxX11GL { + static const int maxGamepadDevNodeLength = 256; + static const int maxGamepadNameLength = 256; + struct LinuxGamepad + { + int fd = -1; + bool initialized = false; + char devNode[maxGamepadDevNodeLength] = {0}; + char name[maxGamepadNameLength] = {0}; + + union + { + struct input_absinfo absInfos[6]; + struct + { + struct input_absinfo leftStickX, leftStickY; + struct input_absinfo rightStickX, rightStickY; + struct input_absinfo leftTrigger, rightTrigger; + }; + }; + + void Init(int fd, const char* name, const char* devNode) + { + this->fd = fd; + // Fill in all the absinfo stuff + //struct input_absinfo absInfo; + + int absArray[] = {ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_Z, ABS_RZ}; + for (unsigned int absIndex = 0; absIndex < (sizeof(absArray) / sizeof(absArray[0])); absIndex++) + { + struct input_absinfo* absInfo = &this->absInfos[absIndex]; + ioctl(this->fd, EVIOCGABS(absArray[absIndex]), absInfo); + } + + memcpy(this->name, name, strlen(name)); + memcpy(this->devNode, devNode, strlen(devNode)); + this->initialized = true; + } + + void DeInit() + { + // close() in here as well? + this->fd = -1; + this->initialized = false; + memset(this->devNode, 0, maxGamepadDevNodeLength); + memset(this->name, 0, maxGamepadNameLength); + } + }; + void * x11Library = nullptr; void * glLibrary = nullptr; @@ -1777,12 +1825,8 @@ namespace Thirteen int hotplugFd = -1; udev_monitor* udevMonitor = nullptr; - // Bookkeeping - int gamepadFd[4] = {-1, -1, -1, -1}; - static const int maxDevNodeLength = 256; - char gamepadDevNode[4][maxDevNodeLength] = {0}; + LinuxGamepad gamepads[4] = {}; int gamepadCount = 0; - bool foundOneGamepad = false; bool InitWindow(uint32 width, uint32 height) @@ -2074,7 +2118,7 @@ namespace Thirteen const char* devNode = udev_device_get_devnode(device); if (devNode) //&& devicePath { - printf("devNode = %s\n", devNode); + //printf("devNode = %s\n", devNode); int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); if (possibleGamepadFd >= 0) { @@ -2087,7 +2131,7 @@ namespace Thirteen ioctl(possibleGamepadFd, EVIOCGID, &devId); char name[256] = {0}; ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); - printf("(%s) found (fd=%d).\n", name, possibleGamepadFd); + //printf("(%s) found (fd=%d).\n", name, possibleGamepadFd); // Interrogate the potential gamepad by checking if the device has the BTN_GAMEPAD button // NOTE: We're working with bit arrays when using EVIOCGBIT @@ -2103,13 +2147,14 @@ namespace Thirteen // Check for empty slot for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - if (gamepadFd[gamepadIndex] == -1 && strlen(devNode) < maxDevNodeLength) + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + + if (!gamepad->initialized) { - foundOneGamepad = true; - gamepadFd[gamepadIndex] = possibleGamepadFd; - memcpy(gamepadDevNode[gamepadIndex], devNode, strlen(devNode)); + gamepad->Init(possibleGamepadFd, name, devNode); gamepadCount++; - printf("Initial scan of gamepad (%s) found (fd=%d) with dev node %s\n", name, possibleGamepadFd, gamepadDevNode[gamepadIndex]); + + //printf("Initial scan of gamepad (%s) found (fd=%d) with dev node %s\n", gamepad->name, gamepad->fd, gamepad->devNode); break; } } @@ -2131,15 +2176,18 @@ namespace Thirteen udev_enumerate_unref(udevEnumerator); } - if (foundOneGamepad) +#if 0 + if (gamepadCount > 0) { printf("Found %d gamepad%s\n", gamepadCount, (gamepadCount > 1) ? "s!" : "!"); printf("After the initial scan:\n"); for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - printf("fd = %d, devnode = %s\n", gamepadFd[gamepadIndex], gamepadDevNode[gamepadIndex]); + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + printf("fd = %d, devnode = %s\n", gamepad->fd, gamepad->devNode); } } +#endif return true; } @@ -2207,7 +2255,7 @@ namespace Thirteen } // Check if any new gamepads was hotplugged, or if we lost connection - if (udevMonitor && hotplugFd) + if (udevMonitor && hotplugFd >= 0) { struct pollfd pollFd; pollFd.fd = hotplugFd; @@ -2226,22 +2274,23 @@ namespace Thirteen const char* devNode = udev_device_get_devnode(device); if (devNode) { - printf("possible hotplug at %s\n", devNode); + //printf("possible hotplug at %s\n", devNode); int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); - if (possibleGamepadFd > 0 && gamepadCount < 4) + if (possibleGamepadFd >= 0 && gamepadCount < 4) { - printf("possible hotplug fd is %d\n", possibleGamepadFd); + //printf("possible hotplug fd is %d\n", possibleGamepadFd); // Query for info and capabilities struct input_id devId; ioctl(possibleGamepadFd, EVIOCGID, &devId); - char name[256] = {0}; + char name[maxGamepadNameLength] = {0}; ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); // Interrogate the potential gamepad // NOTE: We're working with bit arrays when using EVIOCGBIT unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; bool isGamepad = false; + if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) { unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; @@ -2251,13 +2300,16 @@ namespace Thirteen for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - if (gamepadFd[gamepadIndex] == -1 && strlen(devNode) < maxDevNodeLength) + + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + + if (!gamepad->initialized) { - gamepadFd[gamepadIndex] = possibleGamepadFd; - memcpy(gamepadDevNode[gamepadIndex], devNode, strlen(devNode)); + + gamepad->Init(possibleGamepadFd, name, devNode); gamepadCount++; - printf("Hotplugged a gamepad?\n"); - foundOneGamepad = true; + + //printf("Gamepad hotplugged: (%s) found (fd=%d) with dev node %s\n", gamepad->name, gamepad->fd, gamepad->devNode); break; } } @@ -2272,30 +2324,32 @@ namespace Thirteen // This might be something that got unplugged. for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - if (gamepadFd[gamepadIndex] != -1) + // TODO: !!! + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + if (gamepad->initialized) { - bool isTheUnpluggedGamepad = strcmp(gamepadDevNode[gamepadIndex], devNode) == 0; + bool isTheUnpluggedGamepad = strcmp(gamepad->devNode, devNode) == 0; if (isTheUnpluggedGamepad) { - close(gamepadFd[gamepadIndex]); + close(gamepad->fd); // Reset this slot - gamepadFd[gamepadIndex] = -1; - memset(gamepadDevNode[gamepadIndex], 0, maxDevNodeLength); - + //printf("Unplugged %s\n", gamepad->name); + gamepad->DeInit(); gamepadCount--; - printf("Hotunplugged a gamepad?\n"); break; } } } } - - printf("After udev monitor:\n"); +#if 0 + printf("Gamepad states after udev monitor:\n"); for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - printf("fd = %d, devnode = %s\n", gamepadFd[gamepadIndex], gamepadDevNode[gamepadIndex]); + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + printf("name: %s, fd = %d, devnode = %s\n", gamepad->name, gamepad->fd, gamepad->devNode); } +#endif } } @@ -2366,25 +2420,26 @@ namespace Thirteen bool Render(const uint8* pixels, uint32, uint32, bool) { - for (int controllerIndex = 0; platform->foundOneGamepad && (controllerIndex < 4); ++controllerIndex) + for (int controllerIndex = 0; controllerIndex < 4; ++controllerIndex) { // Future reference: evdev emits event, unlike the state-based win32 XInput. Therefore, do no zero the current state. //controllers[controllerIndex] = ControllerState{}; - if (platform->gamepadFd[controllerIndex] == -1) continue; + PlatformLinuxX11GL::LinuxGamepad* gamepad = &platform->gamepads[controllerIndex]; ControllerState* controller = &controllers[controllerIndex]; - struct input_event events[32]; - + if (gamepad->fd == -1) continue; + struct pollfd pollFd; - pollFd.fd = platform->gamepadFd[controllerIndex]; + pollFd.fd = gamepad->fd; pollFd.events = POLLIN; pollFd.revents = 0; int pollResult = poll(&pollFd, 1, 0); if (pollResult > 0) { - int bytesRead = read(platform->gamepadFd[controllerIndex], &events, sizeof(events)); + struct input_event events[32]; + int bytesRead = read(gamepad->fd, &events, sizeof(events)); //printf("slot %d (fd=%d): poll hit, read %d bytes\n", controllerIndex, pollFd.fd, bytesRead); if(bytesRead > 0) @@ -2393,7 +2448,6 @@ namespace Thirteen int eventCount = bytesRead / sizeof(struct input_event); for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) { - //printf(" event type=%d code=%d value=%d\n", events[eventIndex].type, events[eventIndex].code, events[eventIndex].value); struct input_event* event = &events[eventIndex]; if (event->type == EV_KEY) { @@ -2461,13 +2515,11 @@ namespace Thirteen if (event->code == ABS_X) { // X values of the left stick - struct input_absinfo absInfo; - ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); // Map an arbitrary range [absInfo.minimum, absInfo.maximum] to [-1, 1] // by first normalizing to [0, 1] then scale it to [-1, 1] - controller->leftThumbX = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->leftThumbX = (float)(event->value - gamepad->leftStickX.minimum) / (float)(gamepad->leftStickX.maximum - gamepad->leftStickX.minimum); controller->leftThumbX = controller->leftThumbX*2 - 1; // deadzone @@ -2477,22 +2529,18 @@ namespace Thirteen else if (event->code == ABS_Y) { // Y values of the left stick - struct input_absinfo absInfo; - ioctl(pollFd.fd, EVIOCGABS(ABS_Y), &absInfo); - controller->leftThumbY = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->leftThumbY = (float)(event->value - gamepad->leftStickY.minimum) / (float)(gamepad->leftStickY.maximum - gamepad->leftStickY.minimum); controller->leftThumbY = controller->leftThumbY*2 - 1; - int centerY = (absInfo.maximum + absInfo.minimum) / 2; + //int centerY = (gamepad->leftStickY.maximum + gamepad->leftStickY.minimum) / 2; } else if (event->code == ABS_RX) { // X values of the right stick - struct input_absinfo absInfo; - ioctl(pollFd.fd, EVIOCGABS(ABS_X), &absInfo); - controller->rightThumbX = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->rightThumbX = (float)(event->value - gamepad->rightStickX.minimum) / (float)(gamepad->rightStickX.maximum - gamepad->rightStickX.minimum); controller->rightThumbX = controller->rightThumbX*2 - 1; // int centerX = (absInfo.maximum + absInfo.minimum) / 2; @@ -2501,15 +2549,23 @@ namespace Thirteen else if (event->code == ABS_RY) { // Y values of the right stick - struct input_absinfo absInfo; - ioctl(pollFd.fd, EVIOCGABS(ABS_Y), &absInfo); - controller->rightThumbY = (float)(event->value - absInfo.minimum) / (float)(absInfo.maximum - absInfo.minimum); + controller->rightThumbY = (float)(event->value - gamepad->rightStickY.minimum) / (float)(gamepad->rightStickY.maximum - gamepad->rightStickY.minimum); controller->rightThumbY = controller->rightThumbY*2 - 1; //int centerY = (absInfo.maximum + absInfo.minimum) / 2; } + else if (event->code == ABS_Z) + { + // Left trigger + // Don't normalize to [-1, 1], only [0, 1] + controller->leftTrigger = (float)(event->value - gamepad->leftTrigger.minimum) / (float)(gamepad->leftTrigger.maximum - gamepad->leftTrigger.minimum); + } + else if (event->code == ABS_RZ) + { + controller->rightTrigger = (float)(event->value - gamepad->rightTrigger.minimum) / (float)(gamepad->rightTrigger.maximum - gamepad->rightTrigger.minimum); + } } else if (event->type == EV_SYN) { From 10abf19dc46cad10995ae5d42204a42077643cc5 Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Tue, 24 Mar 2026 12:32:15 +0100 Subject: [PATCH 5/6] Third pass --- thirteen.h | 129 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 45 deletions(-) diff --git a/thirteen.h b/thirteen.h index 404cb66..a820d87 100644 --- a/thirteen.h +++ b/thirteen.h @@ -1731,7 +1731,6 @@ namespace Thirteen { this->fd = fd; // Fill in all the absinfo stuff - //struct input_absinfo absInfo; int absArray[] = {ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_Z, ABS_RZ}; for (unsigned int absIndex = 0; absIndex < (sizeof(absArray) / sizeof(absArray[0])); absIndex++) @@ -1740,8 +1739,8 @@ namespace Thirteen ioctl(this->fd, EVIOCGABS(absArray[absIndex]), absInfo); } - memcpy(this->name, name, strlen(name)); - memcpy(this->devNode, devNode, strlen(devNode)); + memcpy(this->name, name, maxGamepadNameLength); + memcpy(this->devNode, devNode, maxGamepadDevNodeLength); this->initialized = true; } @@ -1827,7 +1826,6 @@ namespace Thirteen LinuxGamepad gamepads[4] = {}; int gamepadCount = 0; - bool foundOneGamepad = false; bool InitWindow(uint32 width, uint32 height) { @@ -2051,35 +2049,37 @@ namespace Thirteen return false; } - udev_new = (udev*(*)()) dlsym(libudevLibrary, "udev_new"); - udev_unref = (void(*)(udev *)) dlsym(libudevLibrary, "udev_unref"); - - udev_monitor_new_from_netlink = (udev_monitor*(*)(udev *, const char *)) dlsym(libudevLibrary, "udev_monitor_new_from_netlink"); - udev_monitor_filter_add_match_subsystem_devtype = (int(*)(struct udev_monitor *, const char *, const char *)) dlsym(libudevLibrary, "udev_monitor_filter_add_match_subsystem_devtype"); - udev_monitor_enable_receiving = (int(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_enable_receiving"); - udev_monitor_receive_device = (udev_device*(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_receive_device"); - udev_monitor_get_fd = (int(*)(struct udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_get_fd"); - udev_monitor_ref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_ref"); - udev_monitor_unref = (udev_monitor*(*)(udev_monitor *)) dlsym(libudevLibrary, "udev_monitor_unref"); - - udev_enumerate_new = (udev_enumerate*(*)(udev *)) dlsym(libudevLibrary, "udev_enumerate_new"); - udev_enumerate_add_match_subsystem = (int(*)(udev_enumerate *, const char *)) dlsym(libudevLibrary, "udev_enumerate_add_match_subsystem"); - - udev_enumerate_add_match_property = (int(*)(udev_enumerate *, const char *, const char *)) dlsym(libudevLibrary, "udev_enumerate_add_match_property"); - udev_enumerate_scan_devices = (int(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_scan_devices"); - udev_enumerate_get_list_entry = (udev_list_entry*(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_get_list_entry"); - udev_enumerate_unref = (void(*)(udev_enumerate *)) dlsym(libudevLibrary, "udev_enumerate_unref"); - - udev_list_entry_get_next = (udev_list_entry*(*)(udev_list_entry *)) dlsym(libudevLibrary, "udev_list_entry_get_next"); - udev_list_entry_get_name= (const char*(*)(udev_list_entry *)) dlsym(libudevLibrary, "udev_list_entry_get_name"); - - udev_device_new_from_syspath= (udev_device*(*)(udev *, const char *)) dlsym(libudevLibrary, "udev_device_new_from_syspath"); - udev_device_get_devnode = (const char*(*)(udev_device *)) dlsym(libudevLibrary, "udev_device_get_devnode"); - udev_device_get_property_value = (const char*(*)(udev_device *, const char *)) dlsym(libudevLibrary, "udev_device_get_property_value"); - - udev_device_unref = (void(*)(udev_device *)) dlsym(libudevLibrary, "udev_device_unref"); - - // = (int(*)()) dlsym(libudevLibrary, ""); + auto loadUdevFunction = [&](auto & targetFunctionPointer, const char* name) + { + targetFunctionPointer = (std::remove_reference_t) dlsym(libudevLibrary, name); + return targetFunctionPointer != nullptr; + }; + + if (!loadUdevFunction(udev_new, "udev_new")) return false; + if (!loadUdevFunction(udev_unref, "udev_unref")) return false; + + if (!loadUdevFunction(udev_monitor_new_from_netlink, "udev_monitor_new_from_netlink")) return false; + if (!loadUdevFunction(udev_monitor_filter_add_match_subsystem_devtype, "udev_monitor_filter_add_match_subsystem_devtype")) return false; + if (!loadUdevFunction(udev_monitor_enable_receiving, "udev_monitor_enable_receiving")) return false; + if (!loadUdevFunction(udev_monitor_receive_device, "udev_monitor_receive_device")) return false; + if (!loadUdevFunction(udev_monitor_get_fd, "udev_monitor_get_fd")) return false; + if (!loadUdevFunction(udev_monitor_ref, "udev_monitor_ref")) return false; + if (!loadUdevFunction(udev_monitor_unref, "udev_monitor_unref")) return false; + + if (!loadUdevFunction(udev_enumerate_new, "udev_enumerate_new")) return false; + if (!loadUdevFunction(udev_enumerate_add_match_subsystem, "udev_enumerate_add_match_subsystem")) return false; + if (!loadUdevFunction(udev_enumerate_add_match_property, "udev_enumerate_add_match_property")) return false; + if (!loadUdevFunction(udev_enumerate_scan_devices, "udev_enumerate_scan_devices")) return false; + if (!loadUdevFunction(udev_enumerate_get_list_entry, "udev_enumerate_get_list_entry")) return false; + if (!loadUdevFunction(udev_enumerate_unref, "udev_enumerate_unref")) return false; + + if (!loadUdevFunction(udev_list_entry_get_next, "udev_list_entry_get_next")) return false; + if (!loadUdevFunction(udev_list_entry_get_name, "udev_list_entry_get_name")) return false; + + if (!loadUdevFunction(udev_device_new_from_syspath, "udev_device_new_from_syspath")) return false; + if (!loadUdevFunction(udev_device_get_devnode, "udev_device_get_devnode")) return false; + if (!loadUdevFunction(udev_device_get_property_value, "udev_device_get_property_value")) return false; + if (!loadUdevFunction(udev_device_unref, "udev_device_unref")) return false; udev* udevCtx = udev_new(); if (udevCtx) @@ -2176,6 +2176,8 @@ namespace Thirteen udev_enumerate_unref(udevEnumerator); } + udev_unref(udevCtx); // Should we do this even if we use udevMonitor? Does it create its own context? + #if 0 if (gamepadCount > 0) { @@ -2254,14 +2256,14 @@ namespace Thirteen } } - // Check if any new gamepads was hotplugged, or if we lost connection + // Check if any new gamepads was plugged/unplugged during runtime if (udevMonitor && hotplugFd >= 0) { struct pollfd pollFd; pollFd.fd = hotplugFd; pollFd.events = POLLIN; pollFd.revents = 0; - + if (poll(&pollFd, 1, 0) > 0) { struct udev_device* device = udev_monitor_receive_device(udevMonitor); @@ -2280,8 +2282,9 @@ namespace Thirteen { //printf("possible hotplug fd is %d\n", possibleGamepadFd); // Query for info and capabilities - struct input_id devId; - ioctl(possibleGamepadFd, EVIOCGID, &devId); + + //struct input_id devId; + //ioctl(possibleGamepadFd, EVIOCGID, &devId); char name[maxGamepadNameLength] = {0}; ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); @@ -2300,7 +2303,6 @@ namespace Thirteen for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - LinuxGamepad* gamepad = &gamepads[gamepadIndex]; if (!gamepad->initialized) @@ -2315,13 +2317,10 @@ namespace Thirteen } } } - - if (!isGamepad) - close(possibleGamepadFd); } else { - // This might be something that got unplugged. + // If we can't open the fd, then this might be something that got unplugged for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { // TODO: !!! @@ -2333,8 +2332,6 @@ namespace Thirteen { close(gamepad->fd); - // Reset this slot - //printf("Unplugged %s\n", gamepad->name); gamepad->DeInit(); gamepadCount--; break; @@ -2342,6 +2339,21 @@ namespace Thirteen } } } + + // Check if we're using possibleGamepadFd, if not, close it + bool pluggedIn = false; + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + if (possibleGamepadFd == gamepad->fd) + { + pluggedIn = true; + break; + } + } + + if(!pluggedIn) + close(possibleGamepadFd); #if 0 printf("Gamepad states after udev monitor:\n"); for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) @@ -2389,6 +2401,18 @@ namespace Thirteen dlclose(glLibrary); dlclose(x11Library); + udev_monitor_unref(udevMonitor); + dlclose(libudevLibrary); + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + + if (gamepad->initialized) + { + gamepad->DeInit(); + gamepadCount--; + } + } } bool DoRender(const uint8* pixels) @@ -2428,7 +2452,11 @@ namespace Thirteen PlatformLinuxX11GL::LinuxGamepad* gamepad = &platform->gamepads[controllerIndex]; ControllerState* controller = &controllers[controllerIndex]; - if (gamepad->fd == -1) continue; + if (gamepad->fd == -1) + { + *controller = ControllerState{}; + continue; + } struct pollfd pollFd; pollFd.fd = gamepad->fd; @@ -2577,6 +2605,17 @@ namespace Thirteen } } } + else if (bytesRead < 0) + { + if (errno == ENODEV) + { + close(gamepad->fd); + gamepad->DeInit(); + *controller = ControllerState{}; + platform->gamepadCount--; + continue; + } + } } } From 05c0833de6ea04a8477e8cb48b57a27397e7ecb3 Mon Sep 17 00:00:00 2001 From: BabakSamimi Date: Tue, 24 Mar 2026 17:46:16 +0100 Subject: [PATCH 6/6] Cleaning up a little --- thirteen.h | 270 ++++++++++++++++++++++++++--------------------------- 1 file changed, 132 insertions(+), 138 deletions(-) diff --git a/thirteen.h b/thirteen.h index 1d12878..a370256 100644 --- a/thirteen.h +++ b/thirteen.h @@ -1731,7 +1731,8 @@ namespace Thirteen void Init(int fd, const char* name, const char* devNode) { this->fd = fd; - // Fill in all the absinfo stuff + + // Fill in all the absinfo stuff, used to normalize sticks and triggers int absArray[] = {ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_Z, ABS_RZ}; for (unsigned int absIndex = 0; absIndex < (sizeof(absArray) / sizeof(absArray[0])); absIndex++) @@ -1747,7 +1748,7 @@ namespace Thirteen void DeInit() { - // close() in here as well? + close(this->fd); this->fd = -1; this->initialized = false; memset(this->devNode, 0, maxGamepadDevNodeLength); @@ -2045,152 +2046,147 @@ namespace Thirteen libudevLibrary = dlopen("libudev.so", RTLD_LAZY | RTLD_LOCAL); } - if (!libudevLibrary) - { - return false; - } - - auto loadUdevFunction = [&](auto & targetFunctionPointer, const char* name) + if (libudevLibrary) { - targetFunctionPointer = (std::remove_reference_t) dlsym(libudevLibrary, name); - return targetFunctionPointer != nullptr; - }; - - if (!loadUdevFunction(udev_new, "udev_new")) return false; - if (!loadUdevFunction(udev_unref, "udev_unref")) return false; - - if (!loadUdevFunction(udev_monitor_new_from_netlink, "udev_monitor_new_from_netlink")) return false; - if (!loadUdevFunction(udev_monitor_filter_add_match_subsystem_devtype, "udev_monitor_filter_add_match_subsystem_devtype")) return false; - if (!loadUdevFunction(udev_monitor_enable_receiving, "udev_monitor_enable_receiving")) return false; - if (!loadUdevFunction(udev_monitor_receive_device, "udev_monitor_receive_device")) return false; - if (!loadUdevFunction(udev_monitor_get_fd, "udev_monitor_get_fd")) return false; - if (!loadUdevFunction(udev_monitor_ref, "udev_monitor_ref")) return false; - if (!loadUdevFunction(udev_monitor_unref, "udev_monitor_unref")) return false; - - if (!loadUdevFunction(udev_enumerate_new, "udev_enumerate_new")) return false; - if (!loadUdevFunction(udev_enumerate_add_match_subsystem, "udev_enumerate_add_match_subsystem")) return false; - if (!loadUdevFunction(udev_enumerate_add_match_property, "udev_enumerate_add_match_property")) return false; - if (!loadUdevFunction(udev_enumerate_scan_devices, "udev_enumerate_scan_devices")) return false; - if (!loadUdevFunction(udev_enumerate_get_list_entry, "udev_enumerate_get_list_entry")) return false; - if (!loadUdevFunction(udev_enumerate_unref, "udev_enumerate_unref")) return false; - - if (!loadUdevFunction(udev_list_entry_get_next, "udev_list_entry_get_next")) return false; - if (!loadUdevFunction(udev_list_entry_get_name, "udev_list_entry_get_name")) return false; - - if (!loadUdevFunction(udev_device_new_from_syspath, "udev_device_new_from_syspath")) return false; - if (!loadUdevFunction(udev_device_get_devnode, "udev_device_get_devnode")) return false; - if (!loadUdevFunction(udev_device_get_property_value, "udev_device_get_property_value")) return false; - if (!loadUdevFunction(udev_device_unref, "udev_device_unref")) return false; - - udev* udevCtx = udev_new(); - if (udevCtx) - { - // TODO: Monitor hotplugged input - udevMonitor = udev_monitor_new_from_netlink(udevCtx, "udev"); - if (udevMonitor) + auto loadUdevFunction = [&](auto & targetFunctionPointer, const char* name) { - udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "input", NULL); - udev_monitor_enable_receiving(udevMonitor); - - hotplugFd = udev_monitor_get_fd(udevMonitor); - } - - // Initial scan - udev_enumerate *udevEnumerator = udev_enumerate_new(udevCtx); - - if (udevEnumerator) + targetFunctionPointer = (std::remove_reference_t) dlsym(libudevLibrary, name); + return targetFunctionPointer != nullptr; + }; + + if (!loadUdevFunction(udev_new, "udev_new")) return false; + if (!loadUdevFunction(udev_unref, "udev_unref")) return false; + + if (!loadUdevFunction(udev_monitor_new_from_netlink, "udev_monitor_new_from_netlink")) return false; + if (!loadUdevFunction(udev_monitor_filter_add_match_subsystem_devtype, "udev_monitor_filter_add_match_subsystem_devtype")) return false; + if (!loadUdevFunction(udev_monitor_enable_receiving, "udev_monitor_enable_receiving")) return false; + if (!loadUdevFunction(udev_monitor_receive_device, "udev_monitor_receive_device")) return false; + if (!loadUdevFunction(udev_monitor_get_fd, "udev_monitor_get_fd")) return false; + if (!loadUdevFunction(udev_monitor_ref, "udev_monitor_ref")) return false; + if (!loadUdevFunction(udev_monitor_unref, "udev_monitor_unref")) return false; + + if (!loadUdevFunction(udev_enumerate_new, "udev_enumerate_new")) return false; + if (!loadUdevFunction(udev_enumerate_add_match_subsystem, "udev_enumerate_add_match_subsystem")) return false; + if (!loadUdevFunction(udev_enumerate_add_match_property, "udev_enumerate_add_match_property")) return false; + if (!loadUdevFunction(udev_enumerate_scan_devices, "udev_enumerate_scan_devices")) return false; + if (!loadUdevFunction(udev_enumerate_get_list_entry, "udev_enumerate_get_list_entry")) return false; + if (!loadUdevFunction(udev_enumerate_unref, "udev_enumerate_unref")) return false; + + if (!loadUdevFunction(udev_list_entry_get_next, "udev_list_entry_get_next")) return false; + if (!loadUdevFunction(udev_list_entry_get_name, "udev_list_entry_get_name")) return false; + + if (!loadUdevFunction(udev_device_new_from_syspath, "udev_device_new_from_syspath")) return false; + if (!loadUdevFunction(udev_device_get_devnode, "udev_device_get_devnode")) return false; + if (!loadUdevFunction(udev_device_get_property_value, "udev_device_get_property_value")) return false; + if (!loadUdevFunction(udev_device_unref, "udev_device_unref")) return false; + + udev* udevCtx = udev_new(); + if (udevCtx) { - udev_enumerate_add_match_subsystem(udevEnumerator, "input"); - udev_enumerate_add_match_property(udevEnumerator, "ID_INPUT_JOYSTICK", "1"); - - udev_enumerate_scan_devices(udevEnumerator); - udev_list_entry* deviceList = udev_enumerate_get_list_entry(udevEnumerator); + // Setup monitoring for runtime plugging/unplugging + udevMonitor = udev_monitor_new_from_netlink(udevCtx, "udev"); + if (udevMonitor) + { + udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "input", NULL); + udev_monitor_enable_receiving(udevMonitor); - udev_list_entry* element = deviceList; + hotplugFd = udev_monitor_get_fd(udevMonitor); + } - for (; element; element = udev_list_entry_get_next(element)) + // Initial scan + udev_enumerate *udevEnumerator = udev_enumerate_new(udevCtx); + if (udevEnumerator) { - if (gamepadCount >= 4) break; + udev_enumerate_add_match_subsystem(udevEnumerator, "input"); + udev_enumerate_add_match_property(udevEnumerator, "ID_INPUT_JOYSTICK", "1"); + udev_enumerate_scan_devices(udevEnumerator); + + udev_list_entry* deviceList = udev_enumerate_get_list_entry(udevEnumerator); - const char* devicePath = udev_list_entry_get_name(element); - udev_device* device = udev_device_new_from_syspath(udevCtx, devicePath); - if (device) + for (udev_list_entry* element = deviceList; element; element = udev_list_entry_get_next(element)) { - const char* devNode = udev_device_get_devnode(device); - if (devNode) //&& devicePath + if (gamepadCount >= 4) break; + + const char* devicePath = udev_list_entry_get_name(element); + if(!devicePath) continue; + + udev_device* device = udev_device_new_from_syspath(udevCtx, devicePath); + if (device) { - //printf("devNode = %s\n", devNode); - int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); - if (possibleGamepadFd >= 0) + const char* devNode = udev_device_get_devnode(device); + if (devNode) //&& devicePath { - // User needs to be in the "input" group for this to work. - // For more info on ioctl and the EVIOC macros: - // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes + //printf("devNode = %s\n", devNode); + int possibleGamepadFd = open(devNode, O_RDONLY | O_NONBLOCK); + if (possibleGamepadFd >= 0) + { + // User needs to be in the "input" group for this to work. + // For more info on ioctl and the EVIOC macros: + // https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes - // Query for info and capabilities - struct input_id devId; - ioctl(possibleGamepadFd, EVIOCGID, &devId); - char name[256] = {0}; - ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); - //printf("(%s) found (fd=%d).\n", name, possibleGamepadFd); + // Query for info and capabilities + struct input_id devId; + ioctl(possibleGamepadFd, EVIOCGID, &devId); + char name[256] = {0}; + ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); + //printf("(%s) found (fd=%d).\n", name, possibleGamepadFd); - // Interrogate the potential gamepad by checking if the device has the BTN_GAMEPAD button - // NOTE: We're working with bit arrays when using EVIOCGBIT - unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; - bool isGamepad = false; + // Interrogate the potential gamepad by checking if the device has the BTN_GAMEPAD button + // NOTE: We're working with bit arrays when using EVIOCGBIT + unsigned char keyBits[(KEY_MAX / 8) + 1] = {0}; + bool isGamepad = false; - if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) - { - unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; - isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); - if (isGamepad) + if (ioctl(possibleGamepadFd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), &keyBits) > 0) { - // Check for empty slot - for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + unsigned char gamepadBit = keyBits[BTN_GAMEPAD / 8]; + isGamepad = gamepadBit & (1UL << (BTN_GAMEPAD % 8)); + if (isGamepad) { - LinuxGamepad* gamepad = &gamepads[gamepadIndex]; - - if (!gamepad->initialized) + // Check for empty slot + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - gamepad->Init(possibleGamepadFd, name, devNode); - gamepadCount++; + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + + if (!gamepad->initialized) + { + gamepad->Init(possibleGamepadFd, name, devNode); + gamepadCount++; - //printf("Initial scan of gamepad (%s) found (fd=%d) with dev node %s\n", gamepad->name, gamepad->fd, gamepad->devNode); - break; + //printf("Initial scan of gamepad (%s) found (fd=%d) with dev node %s\n", gamepad->name, gamepad->fd, gamepad->devNode); + break; + } } - } - // No empty slot available + // No empty slot available + } } - } - if (!isGamepad) - close(possibleGamepadFd); + if (!isGamepad) + close(possibleGamepadFd); + } } + udev_device_unref(device); // Will leak memory otherwise! } - - udev_device_unref(device); // Will leak memory otherwise! } } + udev_enumerate_unref(udevEnumerator); } - - udev_enumerate_unref(udevEnumerator); - } - udev_unref(udevCtx); // Should we do this even if we use udevMonitor? Does it create its own context? + udev_unref(udevCtx); // Should we do this even if we use udevMonitor? Does it create its own context? #if 0 - if (gamepadCount > 0) - { - printf("Found %d gamepad%s\n", gamepadCount, (gamepadCount > 1) ? "s!" : "!"); - printf("After the initial scan:\n"); - for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + if (gamepadCount > 0) { - LinuxGamepad* gamepad = &gamepads[gamepadIndex]; - printf("fd = %d, devnode = %s\n", gamepad->fd, gamepad->devNode); + printf("Found %d gamepad%s\n", gamepadCount, (gamepadCount > 1) ? "s!" : "!"); + printf("After the initial scan:\n"); + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + { + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + printf("[%s] fd = %d, devnode = %s\n", gamepad->name, gamepad->fd, gamepad->devNode); + } } - } #endif + } return true; } @@ -2283,12 +2279,6 @@ namespace Thirteen { //printf("possible hotplug fd is %d\n", possibleGamepadFd); // Query for info and capabilities - - //struct input_id devId; - //ioctl(possibleGamepadFd, EVIOCGID, &devId); - - char name[maxGamepadNameLength] = {0}; - ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); // Interrogate the potential gamepad // NOTE: We're working with bit arrays when using EVIOCGBIT @@ -2309,6 +2299,9 @@ namespace Thirteen if (!gamepad->initialized) { + char name[maxGamepadNameLength] = {0}; + ioctl(possibleGamepadFd, EVIOCGNAME(sizeof(name)), name); + gamepad->Init(possibleGamepadFd, name, devNode); gamepadCount++; @@ -2324,15 +2317,12 @@ namespace Thirteen // If we can't open the fd, then this might be something that got unplugged for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - // TODO: !!! LinuxGamepad* gamepad = &gamepads[gamepadIndex]; if (gamepad->initialized) { bool isTheUnpluggedGamepad = strcmp(gamepad->devNode, devNode) == 0; if (isTheUnpluggedGamepad) { - close(gamepad->fd); - gamepad->DeInit(); gamepadCount--; break; @@ -2341,19 +2331,19 @@ namespace Thirteen } } - // Check if we're using possibleGamepadFd, if not, close it - bool pluggedIn = false; + // Check if we ended up using possibleGamepadFd, if not, close it + bool deviceInUse = false; for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { LinuxGamepad* gamepad = &gamepads[gamepadIndex]; if (possibleGamepadFd == gamepad->fd) { - pluggedIn = true; + deviceInUse = true; break; } } - if(!pluggedIn) + if(!deviceInUse) close(possibleGamepadFd); #if 0 printf("Gamepad states after udev monitor:\n"); @@ -2402,16 +2392,20 @@ namespace Thirteen dlclose(glLibrary); dlclose(x11Library); - udev_monitor_unref(udevMonitor); - dlclose(libudevLibrary); - for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) + + if(libudevLibrary) { - LinuxGamepad* gamepad = &gamepads[gamepadIndex]; - - if (gamepad->initialized) + udev_monitor_unref(udevMonitor); + dlclose(libudevLibrary); + for (int gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { - gamepad->DeInit(); - gamepadCount--; + LinuxGamepad* gamepad = &gamepads[gamepadIndex]; + + if (gamepad->initialized) + { + gamepad->DeInit(); + gamepadCount--; + } } } } @@ -2447,7 +2441,7 @@ namespace Thirteen { for (int controllerIndex = 0; controllerIndex < 4; ++controllerIndex) { - // Future reference: evdev emits event, unlike the state-based win32 XInput. Therefore, do no zero the current state. + // Future reference: evdev emits event, unlike the state-based win32 XInput. Therefore, do no zero the current state by default. //controllers[controllerIndex] = ControllerState{}; PlatformLinuxX11GL::LinuxGamepad* gamepad = &platform->gamepads[controllerIndex];