/** * 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 #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[]) { 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; }