#ifdef HAVE_LIBEVDEV #include "evdev_joystick_handler.h" #include "Utilities/Thread.h" #include "Utilities/Log.h" #include #include #include #include #include #include #include #include evdev_joystick_config g_evdev_joystick_config; evdev_joystick_handler::evdev_joystick_handler() {} evdev_joystick_handler::~evdev_joystick_handler() { Close(); } bool evdev_joystick_handler::Init() { g_evdev_joystick_config.load(); axistrigger = static_cast(g_evdev_joystick_config.axistrigger); revaxis.emplace_back(g_evdev_joystick_config.lxreverse); revaxis.emplace_back(g_evdev_joystick_config.lyreverse); revaxis.emplace_back(g_evdev_joystick_config.rxreverse); revaxis.emplace_back(g_evdev_joystick_config.ryreverse); return true; } void evdev_joystick_handler::update_devs() { for (u32 i = 0; i < joy_devs.size(); ++i) try_open_dev(i); } inline u16 Clamp0To255(f32 input) { if (input > 255.f) return 255; else if (input < 0.f) return 0; else return static_cast(input); } std::tuple evdev_joystick_handler::ConvertToSquirclePoint(u16 inX, u16 inY) { // convert inX and Y to a (-1, 1) vector; const f32 x = (inX - 127) / 127.f; const f32 y = ((inY - 127) / 127.f); // compute angle and len of given point to be used for squircle radius const f32 angle = std::atan2(y, x); const f32 r = std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f)); // now find len/point on the given squircle from our current angle and radius in polar coords // https://thatsmaths.com/2016/07/14/squircles/ const f32 newLen = (1 + std::pow(std::sin(2 * angle), 2.f) / (g_evdev_joystick_config.squirclefactor / 1000.f)) * r; // we now have len and angle, convert to cartisian const int newX = Clamp0To255(((newLen * std::cos(angle)) + 1) * 127); const int newY = Clamp0To255(((newLen * std::sin(angle)) + 1) * 127); return std::tuple(newX, newY); } bool evdev_joystick_handler::try_open_dev(u32 index) { libevdev*& dev = joy_devs[index]; bool was_connected = dev != nullptr; if (index >= joy_paths.size()) return false; const auto& path = joy_paths[index]; if (access(path.c_str(), R_OK) == -1) { if (was_connected) { // It was disconnected. pads[index]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES; int fd = libevdev_get_fd(dev); libevdev_free(dev); close(fd); dev = nullptr; } pads[index]->m_port_status &= ~CELL_PAD_STATUS_CONNECTED; LOG_ERROR(GENERAL, "Joystick %s is not present or accessible [previous status: %d]", path.c_str(), was_connected ? 1 : 0); return false; } if (was_connected) return true; // It's already been connected, and the js is still present. int fd = open(path.c_str(), O_RDONLY | O_NONBLOCK); if (fd == -1) { int err = errno; LOG_ERROR(GENERAL, "Failed to open joystick #%d: %s [errno %d]", index, strerror(err), err); return false; } int ret = libevdev_new_from_fd(fd, &dev); if (ret < 0) { LOG_ERROR(GENERAL, "Failed to initialize libevdev for joystick #%d: %s [errno %d]", index, strerror(-ret), -ret); return false; } LOG_NOTICE(GENERAL, "Opened joystick #%d '%s' at %s (fd %d)", index, libevdev_get_name(dev), path, fd); if (!was_connected) // Connection status changed from disconnected to connected. pads[index]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES; pads[index]->m_port_status |= CELL_PAD_STATUS_CONNECTED; int buttons = 0; for (u32 i = BTN_JOYSTICK; i < KEY_MAX; i++) if (libevdev_has_event_code(dev, EV_KEY, i)) { LOG_NOTICE(GENERAL, "Joystick #%d has button %d as %d", index, i, buttons); joy_button_maps[index][i - BTN_MISC] = buttons++; } int axes = 0; for (u32 i = ABS_X; i <= ABS_RZ; i++) { if (libevdev_has_event_code(dev, EV_ABS, i)) { LOG_NOTICE(GENERAL, "Joystick #%d has axis %d as %d", index, i, axes); axis_ranges[i].first = libevdev_get_abs_minimum(dev, i); axis_ranges[i].second = libevdev_get_abs_maximum(dev, i); // Skip ABS_Z and ABS_RZ on controllers where it's used for the triggers. if (axistrigger && (i == ABS_Z || i == ABS_RZ)) continue; joy_axis_maps[index][i - ABS_X] = axes++; } } for (u32 i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) if (libevdev_has_event_code(dev, EV_ABS, i) || libevdev_has_event_code(dev, EV_ABS, i+1)) { LOG_NOTICE(GENERAL, "Joystick #%d has hat %d", index, i); joy_hat_ids[index] = i - ABS_HAT0X; } return true; } void evdev_joystick_handler::Close() { for (auto& dev : joy_devs) { if (dev != nullptr) { int fd = libevdev_get_fd(dev); libevdev_free(dev); close(fd); } } } inline float deadzone(f32 input) { //when we're just above the deadzone, the output should be 128, not 128+deadzone //otherwise, there'll be a jump. So, we need to recalculate the output with a linear function float deadzone_slope, deadzone_origin; if (input >= 127.5 - g_evdev_joystick_config.deadzone && input <= 127.5 + g_evdev_joystick_config.deadzone) { return 127.5; } else { if (input > 127.5 + g_evdev_joystick_config.deadzone) { deadzone_slope = 127.5 / (127.5 - g_evdev_joystick_config.deadzone); deadzone_origin = 255.0 * (1.0 - deadzone_slope); return (deadzone_slope * input + deadzone_origin); } if (input < 127.5 - g_evdev_joystick_config.deadzone) { return (127.5 / (127.5 - g_evdev_joystick_config.deadzone)) * input; } } } int evdev_joystick_handler::scale_axis(int axis, int value) { auto range = axis_ranges[axis]; // Check if scaling is needed. if (range.first != 0 || range.second != 255) { if (range.first < 0) { // Move the ranges up to make the following calculation actually *work* value += -range.first; range.second += -range.first; range.first = 0; } return (deadzone(((static_cast(value) - range.first) / range.second) * 255)); } else { return (deadzone(static_cast(value))); } } std::vector evdev_joystick_handler::ListDevices() { Init(); std::vector evdev_joystick_list; fs::dir devdir{"/dev/input/"}; fs::dir_entry et; while (devdir.read(et)) { // Check if the entry starts with event (a 5-letter word) if (et.name.size() > 5 && et.name.compare(0, 5,"event") == 0) { int fd = open(("/dev/input/" + et.name).c_str(), O_RDONLY|O_NONBLOCK); struct libevdev *dev = NULL; int rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) { // If it's just a bad file descriptor, don't bother logging, but otherwise, log it. if (rc != -9) LOG_WARNING(GENERAL, "Failed to connect to device at %s, the error was: %s", "/dev/input/" + et.name, strerror(-rc)); libevdev_free(dev); close(fd); continue; } if (libevdev_has_event_type(dev, EV_KEY) && libevdev_has_event_code(dev, EV_ABS, ABS_X) && libevdev_has_event_code(dev, EV_ABS, ABS_Y)) { // It's a joystick. evdev_joystick_list.push_back(libevdev_get_name(dev)); } libevdev_free(dev); close(fd); } } return evdev_joystick_list; } bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad, const std::string& device) { Init(); // Now we need to find the device with the same name, and make sure not to grab any duplicates. fs::dir devdir{"/dev/input/"}; fs::dir_entry et; while (devdir.read(et)) { // Check if the entry starts with event (a 5-letter word) if (et.name.size() > 5 && et.name.compare(0, 5,"event") == 0) { int fd = open(("/dev/input/" + et.name).c_str(), O_RDONLY|O_NONBLOCK); struct libevdev *dev = NULL; int rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) { // If it's just a bad file descriptor, don't bother logging, but otherwise, log it. if (rc != -9) LOG_WARNING(GENERAL, "Failed to connect to device at %s, the error was: %s", "/dev/input/" + et.name, strerror(-rc)); libevdev_free(dev); close(fd); continue; } const std::string name = libevdev_get_name(dev); if (libevdev_has_event_type(dev, EV_KEY) && libevdev_has_event_code(dev, EV_ABS, ABS_X) && libevdev_has_event_code(dev, EV_ABS, ABS_Y) && name == device) { // It's a joystick. // Now let's make sure we don't already have this one. bool alreadyIn = false; for (int i = 0; i < joy_paths.size(); i++) if (joy_paths[i] == fmt::format("/dev/input/%s", et.name)) { alreadyIn = true; break; } if (alreadyIn == true) { libevdev_free(dev); close(fd); continue; } // Alright, now that we've confirmed we haven't added this joystick yet, les do dis. joy_paths.emplace_back(fmt::format("/dev/input/%s", et.name)); } libevdev_free(dev); close(fd); } } joy_devs.push_back(nullptr); joy_axis_maps.emplace_back(ABS_RZ - ABS_X, -1); joy_axis.emplace_back(ABS_RZ - ABS_X, -1); joy_button_maps.emplace_back(KEY_MAX - BTN_JOYSTICK, -1); joy_hat_ids.emplace_back(-1); pad->Init( CELL_PAD_STATUS_DISCONNECTED, CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF, CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE, CELL_PAD_DEV_TYPE_STANDARD ); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.triangle, CELL_PAD_CTRL_TRIANGLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.circle, CELL_PAD_CTRL_CIRCLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.cross, CELL_PAD_CTRL_CROSS); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.square, CELL_PAD_CTRL_SQUARE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.l2, CELL_PAD_CTRL_L2); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.r2, CELL_PAD_CTRL_R2); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.l1, CELL_PAD_CTRL_L1); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.r1, CELL_PAD_CTRL_R1); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.start, CELL_PAD_CTRL_START); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.select, CELL_PAD_CTRL_SELECT); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.l3, CELL_PAD_CTRL_L3); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.r3, CELL_PAD_CTRL_R3); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.up, CELL_PAD_CTRL_UP); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.down, CELL_PAD_CTRL_DOWN); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.left, CELL_PAD_CTRL_LEFT); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.right, CELL_PAD_CTRL_RIGHT); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.lxstick, 0, 0); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.lystick, 0, 0); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.rxstick, 0, 0); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.rystick, 0, 0); pad->m_vibrateMotors.emplace_back(true, 0); pad->m_vibrateMotors.emplace_back(false, 0); pads.emplace_back(pad); update_devs(); return true; } void evdev_joystick_handler::ThreadProc() { update_devs(); for (int i = 0; im_buttons.begin(), pad->m_buttons.end(), [&](const Button& bt) { return bt.m_keyCode == button_code; }); if (which_button == pad->m_buttons.end()) { LOG_ERROR(GENERAL, "Joystick #%d sent button event for unmapped button %d", i, evt.code); break; } which_button->m_pressed = evt.value; which_button->m_value = evt.value ? 255 : 0; break; } case EV_ABS: { LOG_NOTICE(GENERAL, "Joystick #%d EV_ABS: %d %d", i, evt.code, evt.value); //duplicate the evt.code and evt.value, so that they're still available //for the analog input int code2 = evt.code; int value2 = evt.value; if (g_evdev_joystick_config.left_analog_to_dpad) { //some gamepad have a -32767 to +32767 range; others have a 0 to 255 float center2 = (axis_ranges[evt.code].second + axis_ranges[evt.code].first) / 2; //arbitrary decision: when the value is higher than a 1/3 of the range, we simulate a dpad press float threshold2 = (axis_ranges[evt.code].second-center2)/3; if (code2 == 0 || code2 == 1) { value2 = 0; if (evt.value > center2 + threshold2) value2 = 1; else if (evt.value < center2 - threshold2) value2 = -1; if (revaxis[code2]) value2 = -value2; if (g_evdev_joystick_config.lxstick < g_evdev_joystick_config.lystick) { code2 = (code2 == 0) ? ABS_HAT0X : ABS_HAT0Y; } else { code2 = (code2 == 0) ? ABS_HAT0Y : ABS_HAT0X; } } } if (code2 >= ABS_HAT0X && code2 <= ABS_HAT3Y) { int hat = code2 - ABS_HAT0X; if (hat != joy_hat_ids[i] && hat-1 != joy_hat_ids[i]) { LOG_ERROR(GENERAL, "Joystick #%d sent HAT event for invalid hat %d (expected %d)", i, hat, joy_hat_ids[i]); break; } int source_axis = hat == joy_hat_ids[i] ? EVDEV_DPAD_HAT_AXIS_X : EVDEV_DPAD_HAT_AXIS_Y; for (Button& bt : pad->m_buttons) { if (bt.m_keyCode != source_axis) continue; if (value2 == 0) { bt.m_pressed = false; bt.m_value = 0; } else { int code; if (source_axis == EVDEV_DPAD_HAT_AXIS_X) { code = value2 > 0 ? CELL_PAD_CTRL_RIGHT : CELL_PAD_CTRL_LEFT; } else { code = value2 > 0 ? CELL_PAD_CTRL_DOWN : CELL_PAD_CTRL_UP; } if (bt.m_outKeyCode == code) { bt.m_pressed = true; bt.m_value = 255; } } } } else if (axistrigger && (evt.code == ABS_Z || evt.code == ABS_RZ)) { // For Xbox controllers, a third axis represent the left/right triggers. int which_trigger = 0; if (evt.code == ABS_Z) { which_trigger = CELL_PAD_CTRL_L2; } else if (evt.code == ABS_RZ) { which_trigger = CELL_PAD_CTRL_R2; } else { LOG_ERROR(GENERAL, "Joystick #%d sent invalid event code %d for 3rd axis", i, evt.code); break; } auto which_button = std::find_if( pad->m_buttons.begin(), pad->m_buttons.end(), [&](const Button& bt) { return bt.m_outKeyCode == which_trigger; }); if (which_button == pad->m_buttons.end()) { LOG_ERROR(GENERAL, "Joystick #%d's pad has no trigger %d", i, which_trigger); break; } int value = scale_axis(evt.code, evt.value); which_button->m_pressed = value > 0; which_button->m_value = value; } if (evt.code >= ABS_X && evt.code <= ABS_RZ) { int axis = joy_axis_maps[i][evt.code - ABS_X]; if (axis > pad->m_sticks.size()) { LOG_ERROR(GENERAL, "Joystick #%d sent axis event for invalid axis %d", i, axis); break; } int value = evt.value; if (revaxis[axis]) { // Reverse the value in the range. value = (axis_ranges[evt.code].second + axis_ranges[evt.code].first) - value; } if (g_evdev_joystick_config.squirclejoysticks) { joy_axis[i][axis] = value; if (evt.code == ABS_X || evt.code == ABS_Y) { int Xaxis = joy_axis_maps[i][ABS_X]; int Yaxis = joy_axis_maps[i][ABS_Y]; pad->m_sticks[Xaxis].m_value = scale_axis(ABS_X, joy_axis[i][Xaxis]); pad->m_sticks[Yaxis].m_value = scale_axis(ABS_Y, joy_axis[i][Yaxis]); std::tie(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value) = ConvertToSquirclePoint(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value); } else { int Xaxis = joy_axis_maps[i][ABS_RX]; int Yaxis = joy_axis_maps[i][ABS_RY]; pad->m_sticks[Xaxis].m_value = scale_axis(ABS_RX, joy_axis[i][Xaxis]); pad->m_sticks[Yaxis].m_value = scale_axis(ABS_RY, joy_axis[i][Yaxis]); std::tie(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value) = ConvertToSquirclePoint(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value); } } else pad->m_sticks[axis].m_value = scale_axis(evt.code, value); } break; } default: LOG_ERROR(GENERAL, "Unknown joystick #%d event %d", i, evt.type); break; } } } #endif