Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Reads joystick/gamepad events on Linux and displays them.
/**
* 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 <stdio.h>
#include <unistd.h>
#include <linux/joystick.h>
/**
* 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 axis_state {
short x, y;
};
/**
* 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;
}
return axis;
}
int main(int argc, char *argv[])
{
const char *device;
int js;
struct js_event event;
struct axis_state axes[3] = {0};
size_t axis;
if (argc > 1)
device = argv[1];
else
device = "/dev/input/js0";
js = open(device, O_RDONLY);
if (js == -1)
perror("Could not open joystick");
/* This loop will exit if the controller is unplugged. */
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;
default:
/* Ignore init events. */
break;
}
fflush(stdout);
}
close(js);
return 0;
}
@tomek-szczesny

This comment has been minimized.

Copy link

@tomek-szczesny tomek-szczesny commented Jun 8, 2019

In line 124, I had to change "%u" into "%zu" to compile this code. Otherwise I got a warning:

joystick.c: In function ‘main’: joystick.c:124:21: warning: format ‘%u’ expects argument of type ‘unsigned int’, but argument 2 has type ‘size_t’ [-Wformat=] printf("Axis %u at (%6d, %6d)\n", axis, axes[axis].x, axes[axis].y); ^

Perhaps casting "axis" into int would be the safest solution, but I suck at programming, so...

Anyway, lovely piece of code. I'm repairing a joystick. :D

@jasonwhite

This comment has been minimized.

Copy link
Owner Author

@jasonwhite jasonwhite commented Jun 10, 2019

@tomek-szczesny Thanks! I updated the Gist with your fix. Glad you found this useful.

@Tetrastorm

This comment has been minimized.

Copy link

@Tetrastorm Tetrastorm commented Aug 8, 2019

Nice code.
You will try to put "{" and "}" at all if and else, although is not needed.

@Hermann-SW

This comment has been minimized.

Copy link

@Hermann-SW Hermann-SW commented Oct 17, 2019

Nice!
Had to add #include <unistd.h> to compile.
Knows all keys of SNES:

Button 0 pressed
Button 0 released
Button 1 pressed
Button 1 released
Button 2 pressed
Button 2 released
Button 3 pressed
Button 3 released
Button 4 pressed
Button 4 released
Button 5 pressed
Button 5 released
Button 8 pressed
Button 8 released
Button 9 pressed
Button 9 released
Axis 0 at (     0, -32767)
Axis 0 at (     0,      0)
Axis 0 at ( 32767,      0)
Axis 0 at (     0,      0)
Axis 0 at (     0,  32767)
Axis 0 at (     0,      0)
Axis 0 at (-32767,      0)
Axis 0 at (     0,      0)
@jasonwhite

This comment has been minimized.

Copy link
Owner Author

@jasonwhite jasonwhite commented Oct 17, 2019

@Hermann-SW Thanks. I've updated the Gist to include unistd.h. 👍

@Hermann-SW

This comment has been minimized.

Copy link

@Hermann-SW Hermann-SW commented Oct 19, 2019

Thank you!
I do use your joystick.c in a bash script that controls 4DoF robot arm with SNES gamepad and it works wonderful.

...
while IFS= read -r line; do
#    echo "Text read from file: $line"
    case $line in
        "Axis 0 at (-32767,      0)")
            let p=p-d
        ;;
        "Axis 0 at ( 32767,      0)")
            let p=p+d
        ;;
...
    pigs s 8 $g; pigs s 9 $u; pigs s 10 $l; pigs s 11 $p

done < <(./joystick)

There is only a last minimal change needed for that to work, and that is flushing the output.
I would like to use your joystick.c unchanged so that I can simply point to your gist.
Can you add this one statement?

$ diff -c3 joystick.c.orig joystick.c
*** joystick.c.orig	2019-10-19 07:11:31.762769671 +0200
--- joystick.c	2019-10-19 07:12:00.047928028 +0200
***************
*** 128,133 ****
--- 128,134 ----
                  /* Ignore init events. */
                  break;
          }
+         fflush(stdout);
      }
  
      close(js);
$ 

This is hardcoded robot arm movement sample, I can do this with SNES gamepad now as well:
https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=252272&p=1553313#p1553313
4DoF_robot_arm_in_action_yes_hard_scripted

@jasonwhite

This comment has been minimized.

Copy link
Owner Author

@jasonwhite jasonwhite commented Oct 19, 2019

@Hermann-SW Wow! That's awesome! I also added the fflush(stdout); 👍

@Hermann-SW

This comment has been minimized.

Copy link

@Hermann-SW Hermann-SW commented Oct 20, 2019

Thanks Jason!
I created initial new github repo for 4DoF robot arm.
tools/gamepad is a bash script mixed with your current version of joystick.c, to control the robot arm.
The exact version is referenced at top of tool.
I really like the 1-liner that does compile your joystick.c in case it is not already available in /tmp/joystick:

#!/bin/bash
# contains and uses this version of Jason White's joystick.c at bottom
# https://gist.github.com/jasonwhite/c5b2048c15993d285130/eb3bcd6e1cd88002d21764b9965d075eceaf4b83
#
# requires SNES joystick connected to Raspberry USB, pigpio library installed
#
js=/tmp/joystick
if [ ! -f $js ]; then sed -n "/^\/\*\*$/,\$p" $0 | gcc -x c - -o $js; fi

g=1500
...
@voltagex

This comment has been minimized.

Copy link

@voltagex voltagex commented Nov 23, 2019

Note that this doesn't seem to detect the D-Pad on the Xbox One controller connected using https://github.com/atar-axis/xpadneo

@geajack

This comment has been minimized.

Copy link

@geajack geajack commented Jun 20, 2020

For me this didn't work. The call to read at line 31 returned -1 and perror reported Invalid argument. To get this to work I had to use input_event instead of js_event, but of course then the rest of the code breaks (for instance input_event doesn't have a number field).

EDIT: Nevermind! It seems I was using the wrong /dev file. Reading from

``/dev/input/by-id/usb-Logitech_Logitech_RumblePad_2_USB-event-joystick````

gets me a stream of input_events (which therefore doesn't work with this code), whereas reading from

/dev/input/by-id/usb-Logitech_Logitech_RumblePad_2_USB-joystick

gets me a stream of js_events as expected by this code.

(Ubuntu 19.10 64 bit with a Logitech gamepad)

@nvlled

This comment has been minimized.

Copy link

@nvlled nvlled commented Aug 6, 2020

My dpad doesn't get detected.

@berton7

This comment has been minimized.

Copy link

@berton7 berton7 commented Oct 3, 2020

My dpad doesn't get detected as well. I'm on opensuse tumbleweed, controller is a wired xbox 360 controller. When I press any dpad button I only see some axis updates, but they don't correspond at all to my actions.

@nvlled

This comment has been minimized.

Copy link

@nvlled nvlled commented Oct 4, 2020

@berton7 I got the dpad working. get_axis_state only checks axis < 3, but the dpad is axis 3. You only need to make small changes to make it work. I'm using a 8bitdo SN30 controller. It should be something like this:

  if (event->type == JS_EVENT_AXIS) {
     if (event->number == 6 ) {
         if (event->value == -32767)
         {
             // left
         }
         else if (event->value == 32767)
         {
             // right
         }
     } else if (event->number == 7) {
         if (event->value == -32767)
         {
             // up
         }
         else if (event->value == 32767)
         {
             // down
         }
     }
  }
@berton7

This comment has been minimized.

Copy link

@berton7 berton7 commented Oct 4, 2020

Yeah I figured it out just after sending the comment, like it always happens. Thanks anyways, hopefully this will save up time to somebody else. Also it would be nice to at least leave a comment in the code about this @jasonwhite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.