#include #ifdef UNIX #include #include #include #endif #ifdef WIN32 //#include #endif #include #include #include #include #include #include #include struct axis_state { short x, y; }; int js_handle; bool js_connected; struct js_event event; struct axis_state axes[3] = {0}; bool btn_state[10] = {false}; size_t axis; Vector2 l_thumb; Vector2 r_thumb; short l_trigger; short r_trigger; Vector2 dpad; jstick::ControllerType device_type; /// Reads a joystick event from the joystick device. /// @returns 0 on success. Otherwise -1 is returned. int read_event(int fd, struct js_event *event) { ssize_t bytes; bytes = read(fd, event, sizeof(*event)); if (bytes == sizeof(*event)) return 0; /// Error, could not read full event. return -1; } std::string js_device_file(int hwid) { return std::format("/dev/input/js{}", hwid); } bool jstick::JoystickDetected(int jsHandle) { return std::filesystem::exists(js_device_file(jsHandle)); } int jstick::NumJoysticksDetected() { int max = 4; for (int i = 0; i < max; i++) { if (!JoystickDetected(i)) return i; } return max; } bool jstick::Connected(int hwid) { return js_connected && js_handle == hwid; } std::string jstick::GetDeviceName(int hwid) { char name_buf[128]; if (ioctl(hwid, JSIOCGNAME(sizeof(name_buf)), name_buf) < 0) strncpy(name_buf, "Unknown", sizeof(name_buf)); return std::string(name_buf); } bool jstick::GetDPadIsAxisOrButtons() { return true; } int jstick::Connect(int hwid) { if (js_connected) { std::cerr << "Joystick already connected! Support for multiple joysticks is in-progress!" << std::endl; return false; } js_handle = open(js_device_file(hwid).c_str(), O_NONBLOCK); if (js_handle == -1) { perror("Could not open joystick"); return false; } js_connected = true; device_type = GetDeviceTypeFromName(GetDeviceName(js_handle)); JoystickConnected.Invoke(js_handle); return js_handle; } bool jstick::Disconnect(int hwid) { if (!js_connected || js_handle == -1) { std::cerr << "Joystick is already disconnected! Support for multiple joysticks is in-progrss!" << std::endl; return false; } close(js_handle); JoystickDisconnected.Invoke(js_handle); js_connected = false; return true; } void jstick::JoystickServiceUpdate() { int device_count = NumJoysticksDetected(); bool has = JoystickDetected(); if (has && !js_connected) Connect(); } void ProcessButtonEvent(uint8_t button_index, bool value) { jstick::XBoxButton btn = (jstick::XBoxButton)button_index; btn_state[button_index] = value; if (value) { jstick::ButtonPressed.Invoke(btn); } else { jstick::ButtonReleased.Invoke(btn); } } void ProcessAxisEvent(uint8_t axis, short value) { // TODO: Handle all single-axis changes, and then invoke the axis event with the new vectors. Vector2 prev_lthumb = l_thumb; Vector2 prev_rthumb = r_thumb; Vector2 prev_dp = dpad; short prev_ltrigger = l_trigger; short prev_rtrigger = r_trigger; if (device_type == jstick::ControllerType::XBox) { if (axis == 0) l_thumb.x = value; if (axis == 1) l_thumb.y = value; if (axis == 2) l_trigger = value; if (axis == 3) r_thumb.x = value; if (axis == 4) r_thumb.y = value; if (axis == 5) r_trigger = value; if (axis == 6) dpad.x = value; if (axis == 7) dpad.y = value; } if (device_type == jstick::ControllerType::PS3) { if (axis == 0) l_thumb.x = value; if (axis == 1) l_thumb.y = value; if (axis == 2) r_thumb.x = value; if (axis == 3) r_thumb.y = value; if (axis == 4) l_trigger = value; if (axis == 5) r_trigger = value; } if (device_type == jstick::ControllerType::PS4) { if (axis == 0) l_thumb.x = value; if (axis == 1) l_thumb.y = value; if (axis == 2) r_thumb.x = value; if (axis == 3) r_thumb.y = value; if (axis == 4) l_trigger = value; if (axis == 5) r_trigger = value; if (axis == 6) dpad.x = value; if (axis == 7) dpad.y = value; } // TODO: Compare against the l_thumb from the last time the event was called. if (l_thumb.DistanceSq(prev_lthumb) > 0.1f) jstick::LeftThumbstickMoved.Invoke(l_thumb); if (r_thumb.DistanceSq(prev_rthumb) > 0.1f) jstick::RightThumbstickMoved.Invoke(r_thumb); if (dpad.DistanceSq(prev_dp) > 0.1f) jstick::DPadMoved.Invoke(dpad); if (Math::Abs(l_trigger) - Math::Abs(prev_ltrigger) > 1.f) jstick::LeftTriggerMoved.Invoke(l_trigger); if (Math::Abs(r_trigger) - Math::Abs(prev_rtrigger) > 1.f) jstick::RightTriggerMoved.Invoke(r_trigger); } void jstick::ReadEventLoop() { while (read_event(js_handle, &event) == 0) { switch (event.type) { case JS_EVENT_BUTTON: ProcessButtonEvent(event.number, event.value != 0); break; case JS_EVENT_AXIS: ProcessAxisEvent(event.number, event.value); break; case JS_EVENT_INIT: default: break; } fflush(stdout); } } bool jstick::IsButtonDown(const XBoxButton &btn) { return btn_state[(uint8_t)btn]; } Vector2 jstick::GetLeftThumbstickAxis(float deadzone) { return l_thumb;} Vector2 jstick::GetRightThumbstickAxis(float deadzone) { return r_thumb; } Vector2 jstick::GetDPadAxis(float deadzone) { return dpad; } short jstick::GetRightTrigger() { return r_trigger;} short jstick::GetLeftTrigger() { return l_trigger; } constexpr float short_range = 32768.f; Vector2 jstick::GetLeftThumbstickAxisNormalized(float deadzone) { return l_thumb / short_range; } Vector2 jstick::GetRightThumbstickAxisNormalized(float deadzone) { return r_thumb / short_range; } Vector2 jstick::GetDPadAxisNormalized(float deadzone) { return dpad / short_range; } float jstick::GetLeftTriggerNormalized() { return l_trigger / short_range; } float jstick::GetRightTriggerNormalized() { return r_trigger / short_range; } jstick::ControllerType jstick::GetDeviceTypeFromName(const std::string &name) { if (name == "Microsoft X-Box 360 pad") return ControllerType::XBox; return ControllerType::Unknown; }