#include "Core/Shared/MessageManager.h" #include "Core/Shared/Emulator.h" #include "Core/Shared/EmuSettings.h" #include "LinuxGameController.h" #include "libevdev/libevdev.h" #include #include #include #include #include #include #include #include std::shared_ptr LinuxGameController::GetController(Emulator* emu, int deviceID, bool logInformation) { std::string deviceName = "/dev/input/event" + std::to_string(deviceID); struct stat buffer; if(stat(deviceName.c_str(), &buffer) == 0) { int fd = open(deviceName.c_str(), O_RDWR | O_NONBLOCK); if(fd < 0) { if(logInformation) { MessageManager::Log("[Input] " + deviceName + " error: " + std::to_string(errno) + " " + strerror(errno)); } return nullptr; } libevdev* device = nullptr; int rc = libevdev_new_from_fd(fd, &device); if(rc < 0) { if(logInformation) { MessageManager::Log("[Input] " + deviceName + " error: " + std::to_string(errno) + " " + strerror(errno)); } close(fd); return nullptr; } if((libevdev_has_event_type(device, EV_KEY) && libevdev_has_event_code(device, EV_KEY, BTN_GAMEPAD)) || (libevdev_has_event_type(device, EV_ABS) && libevdev_has_event_code(device, EV_ABS, ABS_X))) { MessageManager::Log(std::string("[Input Connected] Name: ") + libevdev_get_name(device) + " Vendor: " + std::to_string(libevdev_get_id_vendor(device)) + " Product: " + std::to_string(libevdev_get_id_product(device))); return std::shared_ptr(new LinuxGameController(emu, deviceID, fd, device)); } else { if(logInformation) { MessageManager::Log(std::string("[Input] Device ignored (Not a gamepad) - Name: ") + libevdev_get_name(device) + " Vendor: " + std::to_string(libevdev_get_id_vendor(device)) + " Product: " + std::to_string(libevdev_get_id_product(device))); } close(fd); } } return nullptr; } LinuxGameController::LinuxGameController(Emulator* emu, int deviceID, int fileDescriptor, libevdev* device) { _emu = emu; _deviceID = deviceID; _stopFlag = false; _device = device; _fd = fileDescriptor; memset(_axisDefaultValue, 0, sizeof(_axisDefaultValue)); _rumbleEffect.reset(new ff_effect()); memset(_rumbleEffect.get(), 0, sizeof(ff_effect)); _rumbleEffect->type = FF_RUMBLE; _rumbleEffect->id = -1; _rumbleEffect->u.rumble.strong_magnitude = 0x8000; _rumbleEffect->u.rumble.weak_magnitude = 0x8000; _rumbleEffect->replay.length = 2000; _rumbleEffect->replay.delay = 0; int rc = ioctl(_fd, EVIOCSFF, _rumbleEffect.get()); if(rc < 0) { MessageManager::Log("Could not initialize force feedback effect"); _rumbleEffect.reset(); } _eventThread = std::thread([=]() { int rc; bool calibrate = true; do { fd_set readSet; FD_ZERO(&readSet); FD_SET(_fd, &readSet); //Timeout after 0.1 seconds (to allow thread to be terminated quickly) timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100000; rc = select((int)_fd+1, &readSet, nullptr, nullptr, &timeout); if(rc) { do { struct input_event ev; rc = libevdev_next_event(_device, LIBEVDEV_READ_FLAG_NORMAL, &ev); if(rc == LIBEVDEV_READ_STATUS_SYNC) { while (rc == LIBEVDEV_READ_STATUS_SYNC) { rc = libevdev_next_event(_device, LIBEVDEV_READ_FLAG_SYNC, &ev); } } else if(rc == LIBEVDEV_READ_STATUS_SUCCESS) { //print_event(&ev); } } while(rc == LIBEVDEV_READ_STATUS_SYNC || rc == LIBEVDEV_READ_STATUS_SUCCESS); } if(rc != LIBEVDEV_READ_STATUS_SYNC && rc != LIBEVDEV_READ_STATUS_SUCCESS && rc != -EAGAIN && rc != EWOULDBLOCK) { //Device was disconnected MessageManager::Log("[Input Device] Disconnected"); break; } if(calibrate) { std::this_thread::sleep_for(std::chrono::duration(100)); Calibrate(); calibrate = false; } } while(!_stopFlag); _disconnected = true; }); } LinuxGameController::~LinuxGameController() { _stopFlag = true; _eventThread.join(); libevdev_free(_device); close(_fd); } void LinuxGameController::Calibrate() { int axes[14] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y, ABS_HAT3X, ABS_HAT3Y }; for(int axis : axes) { _axisDefaultValue[axis] = libevdev_get_event_value(_device, EV_ABS, axis); //std::cout << "center values: " << std::to_string(_axisDefaultValue[axis]) << std::endl; } } bool LinuxGameController::CheckAxis(unsigned int code, bool forPositive) { double deadZoneRatio = _emu->GetSettings()->GetControllerDeadzoneRatio(); int deadZoneNegative = (_axisDefaultValue[code] - libevdev_get_abs_minimum(_device, code)) * 0.400 * deadZoneRatio; int deadZonePositive = (libevdev_get_abs_maximum(_device, code) - _axisDefaultValue[code]) * 0.400 * deadZoneRatio; if(forPositive) { return libevdev_get_event_value(_device, EV_ABS, code) - _axisDefaultValue[code] > deadZonePositive; } else { return libevdev_get_event_value(_device, EV_ABS, code) - _axisDefaultValue[code] < -deadZoneNegative; } } bool LinuxGameController::CheckButton(int btn) { return libevdev_get_event_value(_device, EV_KEY, btn) == 1; } bool LinuxGameController::IsButtonPressed(int buttonNumber) { bool pressed = false; switch(buttonNumber) { case 0: pressed = CheckButton(BTN_A); break; case 1: pressed = CheckButton(BTN_B); break; case 2: pressed = CheckButton(BTN_C); break; case 3: pressed = CheckButton(BTN_X); break; case 4: pressed = CheckButton(BTN_Y); break; case 5: pressed = CheckButton(BTN_Z); break; case 6: pressed = CheckButton(BTN_TL); break; case 7: pressed = CheckButton(BTN_TR); break; case 8: pressed = CheckButton(BTN_TL2); break; case 9: pressed = CheckButton(BTN_TR2); break; case 10: pressed = CheckButton(BTN_SELECT); break; case 11: pressed = CheckButton(BTN_START); break; case 12: pressed = CheckButton(BTN_THUMBL); break; case 13: pressed = CheckButton(BTN_THUMBR); break; case 14: pressed = CheckAxis(ABS_X, true); break; case 15: pressed = CheckAxis(ABS_X, false); break; case 16: pressed = CheckAxis(ABS_Y, true); break; case 17: pressed = CheckAxis(ABS_Y, false); break; case 18: pressed = CheckAxis(ABS_Z, true); break; case 19: pressed = CheckAxis(ABS_Z, false); break; case 20: pressed = CheckAxis(ABS_RX, true); break; case 21: pressed = CheckAxis(ABS_RX, false); break; case 22: pressed = CheckAxis(ABS_RY, true); break; case 23: pressed = CheckAxis(ABS_RY, false); break; case 24: pressed = CheckAxis(ABS_RZ, true); break; case 25: pressed = CheckAxis(ABS_RZ, false); break; //PS3 controller maps the D-Pad to BTN_DPAD_x unlike PS4/Xbox controllers, so check those too case 26: pressed = CheckAxis(ABS_HAT0X, true) || CheckButton(BTN_DPAD_RIGHT); break; case 27: pressed = CheckAxis(ABS_HAT0X, false) || CheckButton(BTN_DPAD_LEFT); break; case 28: pressed = CheckAxis(ABS_HAT0Y, true) || CheckButton(BTN_DPAD_DOWN); break; case 29: pressed = CheckAxis(ABS_HAT0Y, false) || CheckButton(BTN_DPAD_UP); break; case 30: pressed = CheckAxis(ABS_HAT1X, true); break; case 31: pressed = CheckAxis(ABS_HAT1X, false); break; case 32: pressed = CheckAxis(ABS_HAT1Y, true); break; case 33: pressed = CheckAxis(ABS_HAT1Y, false); break; case 34: pressed = CheckAxis(ABS_HAT2X, true); break; case 35: pressed = CheckAxis(ABS_HAT2X, false); break; case 36: pressed = CheckAxis(ABS_HAT2Y, true); break; case 37: pressed = CheckAxis(ABS_HAT2Y, false); break; case 38: pressed = CheckAxis(ABS_HAT3X, true); break; case 39: pressed = CheckAxis(ABS_HAT3X, false); break; case 40: pressed = CheckAxis(ABS_HAT3Y, true); break; case 41: pressed = CheckAxis(ABS_HAT3Y, false); break; case 42: pressed = CheckButton(BTN_TRIGGER); break; case 43: pressed = CheckButton(BTN_THUMB); break; case 44: pressed = CheckButton(BTN_THUMB2); break; case 45: pressed = CheckButton(BTN_TOP); break; case 46: pressed = CheckButton(BTN_TOP2); break; case 47: pressed = CheckButton(BTN_PINKIE); break; case 48: pressed = CheckButton(BTN_BASE); break; case 49: pressed = CheckButton(BTN_BASE2); break; case 50: pressed = CheckButton(BTN_BASE3); break; case 51: pressed = CheckButton(BTN_BASE4); break; case 52: pressed = CheckButton(BTN_BASE5); break; case 53: pressed = CheckButton(BTN_BASE6); break; case 54: pressed = CheckButton(BTN_DEAD); break; } _enableForceFeedback |= pressed; return pressed; } optional LinuxGameController::GetAxisPosition(int axis) { axis -= 55; unsigned int code; switch(axis) { default: return std::nullopt; case 0: code = ABS_Y; break; case 1: code = ABS_X; break; case 2: code = ABS_RY; break; case 3: code = ABS_RX; break; case 4: code = ABS_Z; break; case 5: code = ABS_RZ; break; } int min = libevdev_get_abs_minimum(_device, code); int max = libevdev_get_abs_maximum(_device, code); int value = libevdev_get_event_value(_device, EV_ABS, code); int range = max - min; int offset = value - min; double ratio = (double)offset / range; int16_t axisValue = (ratio - 0.5) * 2 * INT16_MAX; return axis & 0x01 ? axisValue : -axisValue; } void LinuxGameController::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft) { if(!_rumbleEffect || !_enableForceFeedback) { return; } _rumbleEffect->u.rumble.strong_magnitude = magnitudeLeft; _rumbleEffect->u.rumble.weak_magnitude = magnitudeRight; int rc = ioctl(_fd, EVIOCSFF, _rumbleEffect.get()); if(rc < 0) { //MessageManager::Log("Could not update force feedback effect."); return; } struct input_event play = {}; play.type = EV_FF; play.code = _rumbleEffect->id; play.value = 1; rc = write(_fd, (const void*)&play, sizeof(play)); if(rc < 0) { //MessageManager::Log("Could not play force feedback effect."); } } bool LinuxGameController::IsDisconnected() { return _disconnected; } int LinuxGameController::GetDeviceID() { return _deviceID; } /* static int print_event(struct input_event *ev) { if (ev->type == EV_SYN) printf("Event: time %ld.%06ld, ++++++++++++++++++++ %s +++++++++++++++\n", ev->time.tv_sec, ev->time.tv_usec, libevdev_event_type_get_name(ev->type)); else printf("Event: time %ld.%06ld, type %d (%s), code %d (%s), value %d\n", ev->time.tv_sec, ev->time.tv_usec, ev->type, libevdev_event_type_get_name(ev->type), ev->code, libevdev_event_code_get_name(ev->type, ev->code), ev->value); return 0; } */