Files
jstick/main.cpp
2025-03-17 19:02:46 -05:00

318 lines
7.6 KiB
C++

/**
* 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
*/
#include <fcntl.h>
#include <cstdio>
#include <unistd.h>
#include <linux/joystick.h>
#include <cstdint>
#include <cstring>
#include <string>
#include <iostream>
#include <filesystem>
#include <format>
#include <jstick.hpp>
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;
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;
*/
}