Code
This commit is contained in:
@@ -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})
|
||||
|
24
cmake/CPM.cmake
Normal file
24
cmake/CPM.cmake
Normal file
@@ -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})
|
@@ -4,32 +4,101 @@
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Event.h>
|
||||
#include <J3ML/LinearAlgebra.hpp>
|
||||
|
||||
// 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<int> JoystickConnected;
|
||||
//Event<int> JoystickDisconnected;
|
||||
//Event<JoystickButton> ButtonPressed;
|
||||
//Event<JoystickButton> 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<int> JoystickConnected;
|
||||
Event<int> JoystickDisconnected;
|
||||
Event<XBoxButton> ButtonPressed;
|
||||
Event<XBoxButton> ButtonReleased;
|
||||
Event<Vector2> LeftThumbstickMoved;
|
||||
Event<Vector2> RightThumbstickMoved;
|
||||
Event<Vector2> DPadMoved;
|
||||
Event<short> LeftTriggerMoved;
|
||||
Event<short> RightTriggerMoved;
|
||||
//Event<int, JoystickAxis> 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();
|
||||
|
||||
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();
|
||||
}
|
6
main.cpp
6
main.cpp
@@ -24,6 +24,7 @@
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <jstick.hpp>
|
||||
|
||||
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;
|
||||
*/
|
||||
}
|
||||
|
160
src/jstick.cpp
160
src/jstick.cpp
@@ -3,18 +3,33 @@
|
||||
#include <linux/joystick.h>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <bits/fs_fwd.h>
|
||||
|
||||
int js_handle;
|
||||
bool js_connected;
|
||||
#include <J3ML/LinearAlgebra/Vector2i.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user