diff --git a/CMakeLists.txt b/CMakeLists.txt index f7ed8b7..b0f34ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,21 @@ if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) message(FATAL_ERROR "In-source builds are not allowed") endif() +include("cmake/CPM.cmake") + +CPMAddPackage( + NAME jlog + URL https://git.redacted.cc/josh/jlog/archive/Prerelease-17.zip +) +CPMAddPackage( + NAME Event + URL https://git.redacted.cc/josh/Event/archive/Release-12.zip +) +CPMAddPackage( + NAME J3ML + URL https://git.redacted.cc/josh/j3ml/archive/3.4.6.zip +) + set(CMAKE_CXX_STANDARD 20) file(GLOB_RECURSE jstick_HEADERS "include/*.hpp") @@ -23,8 +38,16 @@ if (WIN32) add_library(jstick STATIC ${jstick_SRC}) endif() +target_include_directories(jstick PUBLIC + ${Event_SOURCE_DIR}/include + ${jlog_SOURCE_DIR}/include + ${J3ML_SOURCE_DIR}/include +) + target_include_directories(jstick PUBLIC ${jstick_SOURCE_DIR}/include) +target_link_libraries(jstick PUBLIC Event jlog J3ML) + install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME}) install(FILES ${jstick_HEADERS} DESTINATION include/${PROJECT_NAME}) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..68c169b --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.38.7) +set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/include/jstick.hpp b/include/jstick.hpp index a9f2738..b5becab 100644 --- a/include/jstick.hpp +++ b/include/jstick.hpp @@ -4,32 +4,101 @@ /// This work is dedicated to the public domain. #pragma once +#include +#include +#include + +// TODO: Implement simulate DPad Axis button. +// TODO: Make sure Ash applies @ redlobster website. +// TODO: Periodically poll the system to discover device connection and disconnection. namespace jstick { - //Event JoystickConnected; - //Event JoystickDisconnected; - //Event ButtonPressed; - //Event ButtonReleased; + /// A data structure that represents a common symbol for a device button, + /// which has different enum IDs depending on the brand / style of the device. + struct ButtonMapping {}; + + namespace Buttons {} + + class Joystick { + Joystick(); + + public: + protected: + unsigned int hwid; + private: + }; + + enum class ControllerType { + XBox, + PS3, PS4, PS5, + Switch, + Generic, + Unknown, + }; + + 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, + }; + + Event JoystickConnected; + Event JoystickDisconnected; + Event ButtonPressed; + Event ButtonReleased; + Event LeftThumbstickMoved; + Event RightThumbstickMoved; + Event DPadMoved; + Event LeftTriggerMoved; + Event RightTriggerMoved; //Event AxisMoved; - struct Joystick { - int handle; - - }; - - struct JoystickAxis { - float x; - float y; - }; + bool Initialize(); + bool Cleanup(); /// 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 HasActiveJoystick(); - bool InitJoystick(int jsHandle = 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. + bool Connected(int hwid = 0); + /// Attempts to connect and initialize a joystick device with the given Hardware ID. + bool Connect(int hwid = 0); + bool Disconnect(int hwid = 0); int NumJoysticksDetected(); void JoystickServiceUpdate(); void ReadEventLoop(); -} \ No newline at end of file + + bool IsButtonDown(const XBoxButton& btn); + short GetLeftTrigger(); + short GetRightTrigger(); + + + Vector2 GetLeftThumbstickAxis(float deadzone = 1e-2f); + Vector2 GetRightThumbstickAxis(float deadzone = 1e-2f); + Vector2 GetDPadAxis(float deadzone = 1e-2f); + + + // 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); + + float GetLeftTriggerNormalized(); + float GetRightTriggerNormalized(); +} diff --git a/main.cpp b/main.cpp index b937c42..95e4557 100644 --- a/main.cpp +++ b/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include enum class XBoxButton : uint8_t { @@ -228,6 +229,10 @@ void disconnect_device() { int main(int argc, char *argv[]) { + std::cout << jstick::NumJoysticksDetected() << std::endl; + return 0; + + /* const char *device; struct js_event event; @@ -308,4 +313,5 @@ int main(int argc, char *argv[]) close(js); return 0; + */ } diff --git a/src/jstick.cpp b/src/jstick.cpp index 0191f48..72a2ec0 100644 --- a/src/jstick.cpp +++ b/src/jstick.cpp @@ -3,18 +3,33 @@ #include #include #include +#include #include #include +#include #include #include - -int js_handle; -bool js_connected; +#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; + + + /// 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) { @@ -29,9 +44,12 @@ int read_event(int fd, struct js_event *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(std::format("/dev/input/js{}", jsHandle)); + return std::filesystem::exists(js_device_file(jsHandle)); } int jstick::NumJoysticksDetected() { @@ -43,57 +61,104 @@ int jstick::NumJoysticksDetected() { return max; } +bool jstick::Connected(int hwid) { + return js_connected && js_handle == hwid; +} +bool 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; + return true; +} + +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); + 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; -void ProcessAxisEvent(uint8_t axis, short x, short y) { + btn_state[button_index] = value; -} + if (value) { + jstick::ButtonPressed.Invoke(btn); -/// 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 axis_state axes[3]) -{ - 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; + } else { + jstick::ButtonReleased.Invoke(btn); } - - return axis; } -struct js_event event; -struct axis_state axes[3] = {0}; -size_t axis; +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 (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 (l_thumb.DistanceSq(prev_lthumb) > 1.f) + jstick::LeftThumbstickMoved.Invoke(l_thumb); + if (r_thumb.DistanceSq(prev_rthumb) > 1.f) + jstick::RightThumbstickMoved.Invoke(r_thumb); + if (dpad.DistanceSq(prev_dp) > 1.f) + 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 ? true : false); break; case JS_EVENT_AXIS: - axis = get_axis_state(&event, axes); - if (axis < 3) - ProcessAxisEvent(axis, axes[axis].x, axes[axis].y); + ProcessAxisEvent(event.number, event.value); break; case JS_EVENT_INIT: default: @@ -104,3 +169,36 @@ void jstick::ReadEventLoop() { } } +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; +}