diff --git a/CMakeLists.txt b/CMakeLists.txt index c234dcd..077ea24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,18 +26,18 @@ CPMAddPackage( set(CMAKE_CXX_STANDARD 20) file(GLOB_RECURSE jstick_HEADERS "include/*.hpp") -file(GLOB_RECURSE jstick_SRC "src/*.cpp") +file(GLOB_RECURSE SHARED_SOURCES "src/shared/*.cpp") +file(GLOB_RECURSE WINDOWS_SOURCES "src/platform/windows/*.cpp") +file(GLOB_RECURSE LINUX_SOURCES "src/platform/linux/*.cpp") include_directories("include") if (UNIX AND NOT APPLE) - add_library(jstick SHARED ${jstick_SRC}) + add_library(jstick SHARED ${SHARED_SOURCES} ${LINUX_SOURCES}) endif() if (WIN32) - add_library(jstick STATIC ${jstick_SRC} - src/platform/linux/jstick_linux.cpp - src/platform/windows/jstick_xinput.cpp) + add_library(jstick STATIC ${SHARED_SOURCES} ${WINDOWS_SOURCES}) endif() target_include_directories(jstick PUBLIC @@ -48,7 +48,7 @@ target_include_directories(jstick PUBLIC target_include_directories(jstick PUBLIC ${jstick_SOURCE_DIR}/include) -#target_link_libraries(jstick PUBLIC Event jlog J3ML) +target_link_libraries(jstick PUBLIC Event jlog J3ML) install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME}) @@ -56,4 +56,4 @@ install(FILES ${jstick_HEADERS} DESTINATION include/${PROJECT_NAME}) add_executable(jstick-test main.cpp) -#target_link_libraries(jstick-test ${PROJECT_NAME}) \ No newline at end of file +target_link_libraries(jstick-test ${PROJECT_NAME}) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..154d033 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +## Josh Stick API + +A single-header, single-namespace, single API for reading controller and joystick device input on Linux and Windows. + +## Features +* Windows Support via XInput API +* Linux Support via jsdev API + + +## TODO + +* Support multiple devices at once. + * Note that XInput only supports a maximum of 4 devices. \ No newline at end of file diff --git a/include/jstick.hpp b/include/jstick.hpp index fd4a0a3..ffc03c0 100644 --- a/include/jstick.hpp +++ b/include/jstick.hpp @@ -62,6 +62,11 @@ namespace jstick { TheBigOne = 8, ThumbL = 9, ThumbR = 10, + + DPadDown = 11, + DPadRight = 12, + DPadUp = 13, + DPadLeft = 14, }; /// This event is fired when a new joystick device is detected. @@ -87,10 +92,14 @@ namespace jstick { bool Initialize(); bool Cleanup(); + + /// @return true if the input system treats the DPad as a Vector2 axis, or false if treated as four buttons. + bool GetDPadIsAxisOrButtons(); + /// Checks the device files for a joystick with the given handle. /// @return True if the joystick device file exists. /// @note Joystick devices are indexed by an incrementing integer ID, on *nix systems. - bool JoystickDetected(int jsHandle = 0); + bool JoystickDetected(int hwid = 0); /// @return True if there is a connected joystick device with the matching Hardware ID. /// @note jstick supports only one device at a time, currently. @@ -105,23 +114,25 @@ namespace jstick { void JoystickServiceUpdate(); void ReadEventLoop(); - bool IsButtonDown(const XBoxButton& btn); - short GetLeftTrigger(); - short GetRightTrigger(); + bool IsButtonDown(const XBoxButton& btn, int hwid = 0); + short GetLeftTrigger(int hwid = 0); + short GetRightTrigger(int hwid = 0); - Vector2 GetLeftThumbstickAxis(float deadzone = 1e-2f); - Vector2 GetRightThumbstickAxis(float deadzone = 1e-2f); - Vector2 GetDPadAxis(float deadzone = 1e-2f); + Vector2 GetLeftThumbstickAxis(float deadzone = 1e-2f, int hwid = 0); + Vector2 GetRightThumbstickAxis(float deadzone = 1e-2f, int hwid = 0); + Vector2 GetDPadAxis(float deadzone = 1e-2f, int hwid = 0); // TODO: Find a better name for the function set that converts -32768/+32768 range to -1/+1 // TODO: Because Normalization specifically refers to clamping the Axis Vector to Unit Vector range, // TODO: And I wan that set of functions as part of the API as well. - Vector2 GetLeftThumbstickAxisNormalized(float deadzone = 1e-3f); - Vector2 GetRightThumbstickAxisNormalized(float deadzone = 1e-3f); - Vector2 GetDPadAxisNormalized(float deadzone = 1e-3f); + Vector2 GetLeftThumbstickAxisNormalized(float deadzone = 1e-3f, int hwid = 0); + Vector2 GetRightThumbstickAxisNormalized(float deadzone = 1e-3f, int hwid = 0); + Vector2 GetDPadAxisNormalized(float deadzone = 1e-3f, int hwid = 0); - float GetLeftTriggerNormalized(); - float GetRightTriggerNormalized(); + float GetLeftTriggerNormalized(int hwid = 0); + float GetRightTriggerNormalized(int hwid = 0); + + void Vibrate(int LV = 0, int RV = 0, int hwid = 0); } diff --git a/main.cpp b/main.cpp index a0ceea0..e94c92b 100644 --- a/main.cpp +++ b/main.cpp @@ -1,432 +1,76 @@ -/** - * Author: Jason White - * - * Description: - * Reads joystick/gamepad events and displays them. - * - * Compile: - * gcc joystick.c -o joystick - * - * Run: - * ./joystick [/dev/input/jsX] - * - * See also: - * https://www.kernel.org/doc/Documentation/input/joystick-api.txt - */ - - - -#ifdef __linux__ -#include - -#include -#include -#include #include -#include -#include #include -#include -#include -#include - - -enum class XBoxButton : uint8_t -{ - ButtonA = 0, - ButtonB = 1, - ButtonX = 2, - ButtonY = 3, - BumperL = 4, - BumperR = 5, - Back = 6, - Start = 7, - TheBigOne = 8, - ThumbL = 9, - ThumbR = 10, -}; - -/// 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; -} - -/// @returns the number of axes on the controller or 0 if an error occurs. -size_t get_axis_count(int fd) -{ - __u8 axes; - - if (ioctl(fd, JSIOCGAXES, &axes) == -1) - return 0; - - return axes; -} - -/// @returns the number of buttons on the controller or 0 if an error occurs. -size_t get_button_count(int fd) -{ - __u8 buttons; - if (ioctl(fd, JSIOCGBUTTONS, &buttons) == -1) - return 0; - - return buttons; -} - -/// Current state of an axis. - - -struct Axes { - short LeftThumbX = 0; - short LeftThumbY = 0; - short RightThumbX = 0; - short RightThumbY = 0; - short DPadX = 0; - short DPadY = 0; - // TODO Left & Right triggers should start at -32767 because 0 would be pressed half way. - short LeftTrigger = 0; - short RightTrigger = 0; -}; - - -/// Keeps track of the current axis state. -/// @note This function assumes that axes are numbered starting from 0, and that -/// the X axis is an even number, and the Y axis is an odd number. However, this -/// is usually a safe assumption. -/// @returns the axis that the event indicated. -size_t get_axis_state(struct js_event *event, struct Axes& axes) -{ - /// Mappings on my device, meaning these assumptions are incorrect. - // LStick X = 0 - // LStick Y = 1 - // LTrigger = 2 - // RStick X = 3 - // RStick Y = 4 - // RTrigger = 5 - // DirPad X = 6 - // DirPad Y = 7 - - size_t axis = event->number; - short value = event->value; - - // X-Box 360 & X-Box One are the same. - if (axis == 0) axes.LeftThumbX = value; - if (axis == 1) axes.LeftThumbY = value; - if (axis == 2) axes.LeftTrigger = value; - if (axis == 3) axes.RightThumbX = value; - if (axis == 4) axes.RightThumbY = value; - if (axis == 5) axes.RightTrigger = value; - if (axis == 6) axes.DPadX = value; - if (axis == 7) axes.DPadY = value; - - // "Sony PLAYSTATION(R)3 Controller", Has 6 axes. - /* - // D-PAD UP -> Button 12 - // D-Pad DOWN -> Button 13 - // D-Pad LEFT -> Button 14 - // D-Pad RIGHT -> Button 15 - if (axis == 0) axes.LeftThumbX = value; - if (axis == 1) axes.LeftThumbY = value; - if (axis == 3) axes.RightThumbX = value; - if (axis == 4) axes.RightThumbY = value; - if (axis == 2) axes.LeftTrigger = value; - if (axis == 5) axes.RightTrigger = value; - */ - - - // Playstation 4 - /* - if (axis == 0) axes.LeftThumbX = value; - if (axis == 1) axes.LeftThumbY = value; - if (axis == 3) axes.RightThumbX = value; - if (axis == 4) axes.RightThumbY = value; - if (axis == 2) axes.LeftTrigger = value; - if (axis == 5) axes.RightTrigger = value; - if (axis == 6) axes.DPadX = value; - if (axis == 7) axes.DPadY = value; - */ - - // "8BitDo Pro 2" in Direct Input mode. This controller also supports the Xbox mode. - /* - if (axis == 0) axes.LeftThumbX = value; - if (axis == 1) axes.LeftThumbY = value; - if (axis == 2) axes.RightThumbX = value; - if (axis == 3) axes.RightThumbY = value; - if (axis == 4) axes.RightTrigger = value; - if (axis == 5) axes.LeftTrigger = value; - if (axis == 6) axes.DPadX = value; - if (axis == 7) axes.DPadY = value; - */ - - - std::cout << std::format("axis_index: {}, val: {}", axis, value) << std::endl; - std::cout << std::format("lthumb: {},{} ltrigger: {}, rthumb: {},{}, rtrigger: {}, dp: {},{}", axes.LeftThumbX, axes.LeftThumbY, axes.LeftTrigger, axes.RightThumbX, axes.RightThumbY, axes.RightTrigger, axes.DPadX, axes.DPadY) << std::endl; - - /*size_t axis = event->number / 2; - - if (axis < 3) - { - if (event->number % 2 == 0) - axes[axis].x = event->value; - else - axes[axis].y = event->value; - } - - return axis;*/ - return axis; -} - -uint8_t get_num_axes(int joystick_handle) { - char num_axes; - ioctl(joystick_handle, JSIOCGAXES, &num_axes); - return num_axes; -} - -std::string get_joystick_name(int joystick_handle) -{ - char name_buf[128]; - if (ioctl(joystick_handle, JSIOCGNAME(sizeof(name_buf)), name_buf) < 0) - strncpy(name_buf, "Unknown", sizeof(name_buf)); - return std::string(name_buf); -} - -int get_joystick_driver_ver(int joystick_handle) -{ - int version; - ioctl(joystick_handle, JSIOCGVERSION, &version); - return version; -} - -bool check_for_joystick_device() -{ - return std::filesystem::exists("/dev/input/js0"); -} - -int js; -bool device_open; - -bool connect_device() { - js = open("/dev/input/js0", O_NONBLOCK); - - if (js == -1) - perror("Could not open joystick"); - - - std::cout << get_joystick_name(js) << std::endl; - std::cout << get_joystick_driver_ver(js) << std::endl; - std::cout << get_axis_count(js) << std::endl; -} - -void disconnect_device() { - close(js); -} - -#define ENUM_TO_STRING(var) (#var) - -int main(int argc, char *argv[]) -{ - std::cout << jstick::NumJoysticksDetected() << std::endl; - - int hwid = jstick::Connect(0); - - if (hwid != -1) { - - std::cout << jstick::GetDeviceName(hwid) << std::endl; - - - jstick::ButtonPressed += [&](jstick::XBoxButton btn){ - std::cout << std::format("Button Pressed: {}", (int)btn) << std::endl; - }; - - jstick::ButtonReleased += [&](jstick::XBoxButton btn) { - std::cout << std::format("Button Released: {}", (int)btn) << std::endl; - }; - - jstick::DPadMoved += [&](Vector2 v) { - std::cout << std::format("DPad Moved {},{}", v.x, v.y) << std::endl; - }; - - jstick::LeftThumbstickMoved += [&](Vector2 v){ - std::cout << std::format("LThumb Moved {},{}", v.x, v.y) << std::endl; - }; - - jstick::RightThumbstickMoved += [&](Vector2 v){ - std::cout << std::format("RThumb Moved {},{}", v.x, v.y) << std::endl; - }; - - jstick::LeftTriggerMoved += [&] (short val) { - std::cout << std::format("LTrigger Moved {}", val) << std::endl; - }; - - jstick::RightTriggerMoved += [&](short val) { - std::cout << std::format("RTrigger Moved {}", val) << std::endl; - }; - } - - - - while(true) { - jstick::ReadEventLoop(); - } - - return 0; - - /* - const char *device; - - struct js_event event; - struct Axes axes = {}; - size_t axis; - - if (argc > 1) - device = argv[1]; - else - device = "/dev/input/js0"; - - js = open(device, O_NONBLOCK); - - if (js == -1) - perror("Could not open joystick"); - else - { - std::cout << get_joystick_name(js) << std::endl; - std::cout << get_joystick_driver_ver(js) << std::endl; - std::cout << get_axis_count(js) << std::endl; - } - - - - /// This loop will exit if the controller is unplugged. - - bool had = false; - - while (1) - { - bool has_file = check_for_joystick_device(); - - if (has_file && !had) { - if (js == -1) - { - std::cout << "Controller plugged in!" << std::endl; - js = open(device, O_NONBLOCK); - - if (js == -1) - perror("Could not open joystick"); - } - - } else if (!has_file && had) { - std::cout << "Controller unplugged!" << std::endl; - if (js != -1) { - close(js); - js = -1; - } - } - - had = has_file; - - - while (read_event(js, &event) == 0) - { - switch (event.type) - { - case JS_EVENT_BUTTON: - printf("Button %u %s\n", event.number, event.value ? "pressed" : "released"); - break; - case JS_EVENT_AXIS: - axis = get_axis_state(&event, axes); - //if (axis < 3) - //printf("Axis %zu at (%6d, %6d)\n", axis, axes[axis].x, axes[axis].y); - break; - case JS_EVENT_INIT: - default: - /// Ignore init events. - break; - } - - fflush(stdout); - } - } - - - close(js); - return 0; - */ -} -#endif - - -#ifdef WIN32 - -#include -#include -#include - -#pragma comment(lib, "XInput.lib") - -class TController -{ -private: - int n; - XINPUT_STATE state; -public: - TController(int num) - { - n = num; - } - XINPUT_STATE GetState() - { - ZeroMemory(&state, sizeof(XINPUT_STATE)); - XInputGetState(n, &state); - return state; - } - bool IsConnected() - { - ZeroMemory(&state, sizeof(XINPUT_STATE)); - DWORD statenow = XInputGetState(n, &state); - - if(statenow == ERROR_SUCCESS) return true; - - return false; - } - void vibrate(int LV=0, int RV=0) - { - XINPUT_VIBRATION vibration; - ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION)); - - vibration.wLeftMotorSpeed = LV; - vibration.wRightMotorSpeed = RV; - - XInputSetState(n, &vibration); - } -}; +#include +using namespace std::chrono_literals; int main() { - TController* controller0; - controller0 = new TController(0); - if (controller0->IsConnected()) - { - while (controller0->IsConnected()) - { - if(controller0->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_X) - { - std::cout << "A."; - controller0->vibrate(65535, 0); - } - } + bool success = jstick::Initialize(); + + if (!success) { + std::cerr << "Could not initialize jstick!" << std::endl; + return -1; } + + if (!jstick::JoystickDetected()) { + std::cerr << "No devices detected!" << std::endl; + return -1; + } + + while (jstick::JoystickDetected()) { + jstick::ReadEventLoop(); + + if (jstick::IsButtonDown(jstick::XBoxButton::Back)) + std::cout << "Back Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::Start)) + std::cout << "Start Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ButtonA)) + std::cout << "A Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ButtonB)) + std::cout << "B Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ButtonX)) + std::cout << "X Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ButtonY)) + std::cout << "Y Button Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::BumperL)) + std::cout << "Left Bumper Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::BumperR)) + std::cout << "Right Bumper Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ThumbL)) + std::cout << "Left Thumb Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::ThumbR)) + std::cout << "Right Thumb Pressed" << std::endl; + + if (!jstick::GetDPadIsAxisOrButtons()) + { + if (jstick::IsButtonDown(jstick::XBoxButton::DPadUp)) + std::cout << "DPad Up Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::DPadDown)) + std::cout << "DPad Down Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::DPadLeft)) + std::cout << "DPad Left Pressed" << std::endl; + + if (jstick::IsButtonDown(jstick::XBoxButton::DPadRight)) + std::cout << "DPad Right Pressed" << std::endl; + } + + std::this_thread::sleep_for(10ms); + } + + return 0; } -#endif diff --git a/src/jstick_shared.cpp b/src/jstick_shared.cpp deleted file mode 100644 index 00d0c1e..0000000 --- a/src/jstick_shared.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#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); -} - -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; -} diff --git a/src/platform/linux/jstick_linux.cpp b/src/platform/linux/jstick_linux.cpp index 6c003de..16594b8 100644 --- a/src/platform/linux/jstick_linux.cpp +++ b/src/platform/linux/jstick_linux.cpp @@ -1,3 +1,258 @@ -// -// Created by josh on 5/22/2025. -// +#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; +} diff --git a/src/platform/windows/jstick_xinput.cpp b/src/platform/windows/jstick_xinput.cpp index e2be084..5e9a1d2 100644 --- a/src/platform/windows/jstick_xinput.cpp +++ b/src/platform/windows/jstick_xinput.cpp @@ -3,15 +3,30 @@ #include #include +#pragma comment(lib, "XInput.lib") + +#define MAX_DEVICES 4 + +bool device_exists[MAX_DEVICES]; +XINPUT_STATE cur_state[MAX_DEVICES]; +XINPUT_STATE prev_state[MAX_DEVICES]; + bool jstick::Initialize() { - + // TODO: Anything. + return true; } +/// XInput treats the DPad as buttons. +bool jstick::GetDPadIsAxisOrButtons() { return false; } - -bool jstick::JoystickDetected(int jsHandle) +/// XInput does not work via an event-poll loop, so we do nothing here. +void jstick::ReadEventLoop() { - DWORD dwUserIndex = (DWORD)jsHandle; + prev_state[0] = cur_state[0]; + + + + DWORD dwUserIndex = (DWORD)0; XINPUT_STATE state; ZeroMemory(&state, sizeof(XINPUT_STATE)); @@ -20,6 +35,33 @@ bool jstick::JoystickDetected(int jsHandle) if (result == ERROR_SUCCESS) { + cur_state[0] = state; + } +} + + +void jstick::Vibrate(int LV, int RV, int hwid) +{ + + XINPUT_VIBRATION vibration; + ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION)); + + vibration.wLeftMotorSpeed = LV; + vibration.wRightMotorSpeed = RV; + + XInputSetState(hwid, &vibration); +} + +bool jstick::JoystickDetected(int hwid) +{ + DWORD dwUserIndex = (DWORD)hwid; + + XINPUT_STATE state; + ZeroMemory(&state, sizeof(XINPUT_STATE)); + + DWORD result = XInputGetState(dwUserIndex, &state); + + if (result == ERROR_SUCCESS) { return true; } else { std::cout << "Error Code: " << result << std::endl; @@ -27,3 +69,37 @@ bool jstick::JoystickDetected(int jsHandle) } } +bool jstick::IsButtonDown(const XBoxButton& btn, int hwid) +{ + XINPUT_STATE state; + ZeroMemory(&state, sizeof(XINPUT_STATE)); + + DWORD result = XInputGetState(hwid, &state); + + if (result != ERROR_SUCCESS) { + return false; + } + + switch (btn) { + case XBoxButton::Back: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) { return true; } break; + case XBoxButton::Start: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) { return true; } break; + case XBoxButton::ThumbL: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) { return true; } break; + case XBoxButton::ThumbR: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) { return true; } break; + case XBoxButton::ButtonA: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) { return true; } break; + case XBoxButton::ButtonB: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) { return true; } break; + case XBoxButton::ButtonX: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X) { return true; } break; + case XBoxButton::ButtonY: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) { return true; } break; + + case XBoxButton::BumperL: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) { return true; } break; + case XBoxButton::BumperR: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) { return true; } break; + + case XBoxButton::DPadUp: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) { return true; } break; + case XBoxButton::DPadLeft: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { return true; } break; + case XBoxButton::DPadDown: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { return true; } break; + case XBoxButton::DPadRight: if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { return true; } break; + default: return false; + } + + return false; +} + diff --git a/src/shared/jstick_shared.cpp b/src/shared/jstick_shared.cpp new file mode 100644 index 0000000..8a106ba --- /dev/null +++ b/src/shared/jstick_shared.cpp @@ -0,0 +1 @@ +#pragma bruh \ No newline at end of file