diff --git a/CHANGES.md b/CHANGES.md index 55bf4046901..87fea157f7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ - INPUT: Enable Caps, Num, Scroll Lock modifiers on multiple platforms - INPUT: Autoconfig extension with alternative name/vid/pid - INPUT/HID: Fix crash on macOS when disconnecting the controller a second time +- INPUT/LINUX: Add illuminance sensor support to the linuxraw, sdl2, udev, and x11 input drivers - INPUT/Remaps: Sort and apply remaps based on the specific connected controller - INPUT/UDEV: Enable mouse buttons 4 and 5 - INPUT/WAYLAND: Enable horizontal scroll and mouse buttons 4 and 5 diff --git a/input/common/linux_common.c b/input/common/linux_common.c index a29d0561565..784da46f860 100644 --- a/input/common/linux_common.c +++ b/input/common/linux_common.c @@ -14,22 +14,57 @@ * If not, see . */ +#include #include - +#include #include +#include +#include #include #include #include #include #include "linux_common.h" +#include "verbosity.h" +#include +#include +#include + +/* We can assume that pthreads are available on Linux. */ +#include +#include + +#define IIO_DEVICES_DIR "/sys/bus/iio/devices" +#define IIO_ILLUMINANCE_SENSOR "in_illuminance_input" +#define DEFAULT_POLL_RATE 5 /* TODO/FIXME - static globals */ static struct termios old_term, new_term; static long old_kbmd = 0xffff; static bool linux_stdin_claimed = false; +struct linux_illuminance_sensor +{ + sthread_t *thread; + + /* Poll rate in Hz (i.e. in queries per second) */ + volatile unsigned poll_rate; + + /* We store the lux reading in millilux (as an int) + * so that we can make the access atomic and avoid locks. + * A little bit of precision is lost, but not enough to matter. + */ + volatile int millilux; + + /* If true, the associated thread must finish its work and exit. */ + volatile bool done; + + char path[PATH_MAX_LENGTH]; +}; +static double linux_read_illuminance_sensor(const linux_illuminance_sensor_t *sensor); + void linux_terminal_restore_input(void) { if (old_kbmd == 0xffff) @@ -121,3 +156,222 @@ bool linux_terminal_disable_input(void) return true; } + +static void linux_poll_illuminance_sensor(void *data) +{ + linux_illuminance_sensor_t *sensor = data; + + if (!data) + return; + + while (!sensor->done) + { /* Aligned int reads are atomic on most CPUs */ + double lux; + int millilux; + unsigned poll_rate = sensor->poll_rate; + + /* Don't allow cancellation inside the critical section, + * as it opens up a file; we don't want to leak it! */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + lux = linux_read_illuminance_sensor(sensor); + millilux = (int)(lux * 1000.0); + retro_assert(poll_rate != 0); + + sensor->millilux = millilux; + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + /* Allow cancellation here so that the main thread doesn't block + * while waiting for this thread to wake up and exit. */ + retro_sleep(1000 / poll_rate); + if (errno == EINTR) + { + RARCH_ERR("Illuminance sensor thread interrupted\n"); + break; + } + } + + RARCH_DBG("Illuminance sensor thread for %s exiting\n", sensor->path); +} + +linux_illuminance_sensor_t *linux_open_illuminance_sensor(unsigned rate) +{ + DIR *devices = NULL; + struct dirent *d = NULL; + linux_illuminance_sensor_t *sensor = malloc(sizeof(*sensor)); + + if (!sensor) + goto error; + + sensor->millilux = 0; + sensor->poll_rate = rate ? rate : DEFAULT_POLL_RATE; + sensor->thread = NULL; /* We'll spawn a thread later, once we find a sensor */ + sensor->done = false; + + devices = opendir(IIO_DEVICES_DIR); + if (!devices) + { /* If we couldn't find the IIO device directory... */ + char errmesg[NAME_MAX_LENGTH]; + strerror_r(errno, errmesg, sizeof(errmesg)); + RARCH_ERR("Failed to open " IIO_DEVICES_DIR ": %s\n", errmesg); + goto error; + } + + /* + * Must clear errno at the start of each iteration, + * as an error code that came from one run of the loop + * can leak into the next iteration and hide serious errors. + * (Ex: trying to open a file that doesn't exist, + * which we handle here.) + */ + errno = 0; + for (d = readdir(devices); d != NULL; errno = 0, d = readdir(devices)) + { /* For each IIO device... */ + /* First ensure that the readdir call succeeded... */ + int err = errno; + double lux; + + if (d->d_name[0] == '.') + /* Skip hidden files, ".", and ".." */ + continue; + + if (err != 0) + { + char errmesg[NAME_MAX_LENGTH]; + strerror_r(err, errmesg, sizeof(errmesg)); + RARCH_ERR("readdir(" IIO_DEVICES_DIR ") failed: %s\n", errmesg); + goto error; + } + + /* If that worked out, look to see if this device represents an illuminance sensor */ + snprintf(sensor->path, sizeof(sensor->path), IIO_DEVICES_DIR "/%s/" IIO_ILLUMINANCE_SENSOR, d->d_name); + + lux = linux_read_illuminance_sensor(sensor); + if (lux >= 0) + { /* If we found an illuminance sensor that works... */ + sensor->millilux = (int)(lux * 1000.0); /* Set the first reading */ + sensor->thread = sthread_create(linux_poll_illuminance_sensor, sensor); + + if (!sensor->thread) + { + RARCH_ERR("Failed to spawn thread for illuminance sensor\n"); + goto error; + } + + RARCH_LOG("Opened illuminance sensor at %s, polling at %u Hz\n", sensor->path, sensor->poll_rate); + + goto done; + } + } + +error: + RARCH_ERR("Failed to find an illuminance sensor\n"); + if (devices) + closedir(devices); + + free(sensor); + + return NULL; +done: + if (devices) + closedir(devices); + + return sensor; +} + +void linux_close_illuminance_sensor(linux_illuminance_sensor_t *sensor) +{ + if (!sensor) + return; + + if (sensor->thread) + { + pthread_t thread = sthread_get_thread_id(sensor->thread); + sensor->done = true; + + if (pthread_cancel(thread) != 0) + { + int err = errno; + char errmesg[NAME_MAX_LENGTH]; + strerror_r(err, errmesg, sizeof(errmesg)); + RARCH_ERR("Failed to cancel illuminance sensor thread: %s\n", errmesg); + } + + sthread_join(sensor->thread); + /* sthread_join will free the thread */ + } + + free(sensor); +} + +float linux_get_illuminance_reading(const linux_illuminance_sensor_t *sensor) +{ + int millilux; + if (!sensor) + return -1.0f; + + /* Reading an int is atomic on most CPUs */ + millilux = sensor->millilux; + + return (float)millilux / 1000.0f; +} + + +void linux_set_illuminance_sensor_rate(linux_illuminance_sensor_t *sensor, unsigned rate) +{ + if (!sensor) + return; + + /* Set a default rate of 5 Hz if none is provided */ + rate = rate ? rate : DEFAULT_POLL_RATE; + + sensor->poll_rate = rate; +} + +static double linux_read_illuminance_sensor(const linux_illuminance_sensor_t *sensor) +{ + char buffer[256]; + double illuminance = 0.0; + int err; + FILE* in_illuminance_input = NULL; + + if (!sensor || sensor->path[0] == '\0') + return -1.0; + + in_illuminance_input = fopen(sensor->path, "r"); + if (!in_illuminance_input) + { + char errmesg[NAME_MAX_LENGTH]; + strerror_r(errno, errmesg, sizeof(errmesg)); + RARCH_ERR("Failed to open %s: %s\n", sensor->path, errmesg); + illuminance = -1.0; + goto done; + } + + if (!fgets(buffer, sizeof(buffer), in_illuminance_input)) + { /* Read the illuminance value from the file. If that fails... */ + char errmesg[NAME_MAX_LENGTH]; + strerror_r(errno, errmesg, sizeof(errmesg)); + RARCH_ERR("Illuminance sensor read failed: %s\n", errmesg); + illuminance = -1.0; + goto done; + } + + /* TODO: This may be locale-sensitive */ + errno = 0; + illuminance = strtod(buffer, NULL); + err = errno; + if (err != 0) + { + char errmesg[NAME_MAX_LENGTH]; + strerror_r(err, errmesg, sizeof(errmesg)); + RARCH_ERR("Failed to parse input \"%s\" into a floating-point value: %s", buffer, errmesg); + illuminance = -1.0; + goto done; + } + +done: + if (in_illuminance_input) + fclose(in_illuminance_input); + + return illuminance; +} diff --git a/input/common/linux_common.h b/input/common/linux_common.h index b530b71c433..3b3df499d8f 100644 --- a/input/common/linux_common.h +++ b/input/common/linux_common.h @@ -27,4 +27,25 @@ bool linux_terminal_grab_stdin(void *data); bool linux_terminal_disable_input(void); +/** + * Corresponds to the illuminance sensor exposed via the IIO interface. + * @see https://github.com/torvalds/linux/blob/master/Documentation/ABI/testing/sysfs-bus-iio + */ +typedef struct linux_illuminance_sensor linux_illuminance_sensor_t; + +/** + * Iterates through /sys/bus/iio/devices and returns the first illuminance sensor found, + * or NULL if none was found. + * + * @param rate The rate at which to poll the sensor, in Hz. + */ +linux_illuminance_sensor_t *linux_open_illuminance_sensor(unsigned rate); + +void linux_close_illuminance_sensor(linux_illuminance_sensor_t *sensor); + +/** Returns the light sensor's most recent reading in lux, or a negative number on error. */ +float linux_get_illuminance_reading(const linux_illuminance_sensor_t *sensor); + +void linux_set_illuminance_sensor_rate(linux_illuminance_sensor_t *sensor, unsigned rate); + #endif diff --git a/input/drivers/linuxraw_input.c b/input/drivers/linuxraw_input.c index 098b70ab48c..1788c1f283c 100644 --- a/input/drivers/linuxraw_input.c +++ b/input/drivers/linuxraw_input.c @@ -35,6 +35,7 @@ typedef struct linuxraw_input { bool state[0x80]; + linux_illuminance_sensor_t *illuminance_sensor; } linuxraw_input_t; static void *linuxraw_input_init(const char *joypad_driver) @@ -165,9 +166,62 @@ static void linuxraw_input_free(void *data) return; linux_terminal_restore_input(); + linux_close_illuminance_sensor(linuxraw->illuminance_sensor); free(data); } +static bool linuxraw_input_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned rate) +{ + linuxraw_input_t *linuxraw = (linuxraw_input_t*)data; + + if (!linuxraw) + return false; + + switch (action) + { + case RETRO_SENSOR_ILLUMINANCE_DISABLE: + /* If already disabled, then do nothing */ + linux_close_illuminance_sensor(linuxraw->illuminance_sensor); /* noop if NULL */ + linuxraw->illuminance_sensor = NULL; + case RETRO_SENSOR_GYROSCOPE_DISABLE: + case RETRO_SENSOR_ACCELEROMETER_DISABLE: + /** Unimplemented sensor actions that probably shouldn't fail */ + return true; + + case RETRO_SENSOR_ILLUMINANCE_ENABLE: + if (linuxraw->illuminance_sensor) + /* If the light sensor is already open, just set the rate */ + linux_set_illuminance_sensor_rate(linuxraw->illuminance_sensor, rate); + else + linuxraw->illuminance_sensor = linux_open_illuminance_sensor(rate); + + return linuxraw->illuminance_sensor != NULL; + default: + break; + } + + return false; +} + +static float linuxraw_input_get_sensor_input(void *data, unsigned port, unsigned id) +{ + linuxraw_input_t *linuxraw = (linuxraw_input_t*)data; + + if (!linuxraw) + return 0.0f; + + switch (id) + { + case RETRO_SENSOR_ILLUMINANCE: + if (linuxraw->illuminance_sensor) + return linux_get_illuminance_reading(linuxraw->illuminance_sensor); + default: + break; + } + + return 0.0f; +} + static void linuxraw_input_poll(void *data) { uint8_t c; @@ -194,7 +248,7 @@ static void linuxraw_input_poll(void *data) static uint64_t linuxraw_get_capabilities(void *data) { - return (1 << RETRO_DEVICE_JOYPAD) + return (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG); } @@ -203,8 +257,8 @@ input_driver_t input_linuxraw = { linuxraw_input_poll, linuxraw_input_state, linuxraw_input_free, - NULL, - NULL, + linuxraw_input_set_sensor_state, + linuxraw_input_get_sensor_input, linuxraw_get_capabilities, "linuxraw", NULL, /* grab_mouse */ diff --git a/input/drivers/sdl_input.c b/input/drivers/sdl_input.c index 169e92bffa7..83c9419fe32 100644 --- a/input/drivers/sdl_input.c +++ b/input/drivers/sdl_input.c @@ -30,6 +30,10 @@ #include "../../retroarch.h" #include "../../tasks/tasks_internal.h" +#ifdef __linux__ +#include "../common/linux_common.h" +#endif + #ifdef HAVE_SDL2 #include "../../gfx/common/sdl2_common.h" #endif @@ -54,6 +58,10 @@ typedef struct sdl_input int mouse_wd; int mouse_wl; int mouse_wr; +#ifdef __linux__ + /* Light sensors aren't exposed through SDL, and they're not usually part of controllers */ + linux_illuminance_sensor_t *illuminance_sensor; +#endif } sdl_input_t; #ifdef WEBOS @@ -249,7 +257,7 @@ static int16_t sdl_input_state( if (idx == 0) { struct video_viewport vp; - bool screen = device == + bool screen = device == RARCH_DEVICE_POINTER_SCREEN; const int edge_detect = 32700; bool inside = false; @@ -275,7 +283,7 @@ static int16_t sdl_input_state( res_y = res_screen_y; } - inside = (res_x >= -edge_detect) + inside = (res_x >= -edge_detect) && (res_y >= -edge_detect) && (res_x <= edge_detect) && (res_y <= edge_detect); @@ -325,6 +333,7 @@ static void sdl_input_free(void *data) #ifndef HAVE_SDL2 SDL_Event event; #endif + sdl_input_t *sdl = (sdl_input_t*)data; if (!data) return; @@ -336,9 +345,73 @@ static void sdl_input_free(void *data) while (SDL_PollEvent(&event)); #endif +#ifdef __linux__ + linux_close_illuminance_sensor(sdl->illuminance_sensor); /* noop if NULL */ +#endif + free(data); } +static bool sdl_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned rate) +{ + sdl_input_t *sdl = (sdl_input_t*)data; + + if (!sdl) + return false; + + switch (action) + { + case RETRO_SENSOR_ILLUMINANCE_DISABLE: +#ifdef __linux__ + /* If already disabled, then do nothing */ + linux_close_illuminance_sensor(sdl->illuminance_sensor); /* noop if NULL */ + sdl->illuminance_sensor = NULL; +#endif + case RETRO_SENSOR_GYROSCOPE_DISABLE: + case RETRO_SENSOR_ACCELEROMETER_DISABLE: + /** Unimplemented sensor actions that probably shouldn't fail */ + return true; + + case RETRO_SENSOR_ILLUMINANCE_ENABLE: +#ifdef __linux__ + /* Unsupported on non-Linux platforms */ + if (sdl->illuminance_sensor) + /* If we already have a sensor, just set the rate */ + linux_set_illuminance_sensor_rate(sdl->illuminance_sensor, rate); + else + sdl->illuminance_sensor = linux_open_illuminance_sensor(rate); + + return sdl->illuminance_sensor != NULL; +#endif + default: + break; + } + + return false; +} + +static float sdl_get_sensor_input(void *data, unsigned port, unsigned id) +{ + sdl_input_t *sdl = (sdl_input_t*)data; + + if (!sdl) + return 0.0f; + + switch (id) + { + case RETRO_SENSOR_ILLUMINANCE: +#ifdef __linux__ + if (sdl->illuminance_sensor) + return linux_get_illuminance_reading(sdl->illuminance_sensor); +#endif + /* Unsupported on non-Linux platforms */ + default: + break; + } + + return 0.0f; +} + #ifdef HAVE_SDL2 static void sdl2_grab_mouse(void *data, bool state) { @@ -397,7 +470,7 @@ static void sdl_input_poll(void *data) switch ((int) event.key.keysym.scancode) { case SDL_WEBOS_SCANCODE_BACK: - /* Because webOS is sending DOWN/UP at the same time, + /* Because webOS is sending DOWN/UP at the same time, we save this flag for later */ sdl_webos_special_keymap[sdl_webos_spkey_back] |= event.type == SDL_KEYDOWN; code = RETROK_BACKSPACE; @@ -478,8 +551,8 @@ input_driver_t input_sdl = { sdl_input_poll, sdl_input_state, sdl_input_free, - NULL, - NULL, + sdl_set_sensor_state, + sdl_get_sensor_input, sdl_get_capabilities, #ifdef HAVE_SDL2 "sdl2", diff --git a/input/drivers/udev_input.c b/input/drivers/udev_input.c index 294aaa27e0d..bfa5f6afabc 100644 --- a/input/drivers/udev_input.c +++ b/input/drivers/udev_input.c @@ -569,6 +569,8 @@ typedef struct udev_input #ifdef UDEV_XKB_HANDLING bool xkb_handling; #endif + + linux_illuminance_sensor_t *illuminance_sensor; } udev_input_t; #ifdef UDEV_XKB_HANDLING @@ -4013,9 +4015,63 @@ static void udev_input_free(void *data) udev_input_kb_free(udev); + linux_close_illuminance_sensor(udev->illuminance_sensor); + free(udev); } +static bool udev_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned rate) +{ + udev_input_t *udev = (udev_input_t*)data; + + if (!udev) + return false; + + switch (action) + { + case RETRO_SENSOR_ILLUMINANCE_DISABLE: + /* If already disabled, then do nothing */ + linux_close_illuminance_sensor(udev->illuminance_sensor); /* noop if NULL */ + udev->illuminance_sensor = NULL; + case RETRO_SENSOR_GYROSCOPE_DISABLE: + case RETRO_SENSOR_ACCELEROMETER_DISABLE: + /** Unimplemented sensor actions that probably shouldn't fail */ + return true; + + case RETRO_SENSOR_ILLUMINANCE_ENABLE: + if (udev->illuminance_sensor) + /* If we already have a sensor, just set the rate */ + linux_set_illuminance_sensor_rate(udev->illuminance_sensor, rate); + else + udev->illuminance_sensor = linux_open_illuminance_sensor(rate); + + return udev->illuminance_sensor != NULL; + default: + break; + } + + return false; +} + +static float udev_get_sensor_input(void *data, unsigned port, unsigned id) +{ + udev_input_t *udev = (udev_input_t*)data; + + if (!udev) + return 0.0f; + + switch (id) + { + case RETRO_SENSOR_ILLUMINANCE: + if (udev->illuminance_sensor) + return linux_get_illuminance_reading(udev->illuminance_sensor); + default: + break; + } + + return 0.0f; +} + static bool open_devices(udev_input_t *udev, enum udev_input_dev_type type, device_handle_cb cb) { @@ -4227,8 +4283,8 @@ input_driver_t input_udev = { udev_input_poll, udev_input_state, udev_input_free, - NULL, - NULL, + udev_set_sensor_state, + udev_get_sensor_input, udev_input_get_capabilities, "udev", udev_input_grab_mouse, diff --git a/input/drivers/x11_input.c b/input/drivers/x11_input.c index 69d7b68176a..8c6bcad13f3 100644 --- a/input/drivers/x11_input.c +++ b/input/drivers/x11_input.c @@ -27,6 +27,7 @@ #include "../input_keymaps.h" #include "../common/input_x11_common.h" +#include "../common/linux_common.h" #include "../../configuration.h" #include "../../retroarch.h" @@ -45,6 +46,10 @@ typedef struct x11_input bool mouse_l; bool mouse_r; bool mouse_m; +#ifdef __linux__ + /* X11 is mostly used on Linux, but not exclusively. */ + linux_illuminance_sensor_t *illuminance_sensor; +#endif } x11_input_t; /* Public global variable */ @@ -342,7 +347,7 @@ static int16_t x_input_state( x11->mouse_x, x11->mouse_y, &res_x, &res_y, &res_screen_x, &res_screen_y)) { - inside = (res_x >= -edge_detect) + inside = (res_x >= -edge_detect) && (res_y >= -edge_detect) && (res_x <= edge_detect) && (res_y <= edge_detect); @@ -397,10 +402,10 @@ static int16_t x_input_state( joyport, (uint16_t)joykey)) return 1; if (joyaxis != AXIS_NONE && - ((float)abs(joypad->axis(joyport, joyaxis)) + ((float)abs(joypad->axis(joyport, joyaxis)) / 0x8000) > axis_threshold) return 1; - else if ((binds[port][new_id].key && binds[port][new_id].key < RETROK_LAST) + else if ((binds[port][new_id].key && binds[port][new_id].key < RETROK_LAST) && !keyboard_mapping_blocked && x_keyboard_pressed(x11, binds[port][new_id].key) ) @@ -431,7 +436,70 @@ static void x_input_free(void *data) x11_input_t *x11 = (x11_input_t*)data; if (x11) + { +#ifdef __linux__ + linux_close_illuminance_sensor(x11->illuminance_sensor); +#endif free(x11); + } +} + +static bool x_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned rate) +{ + x11_input_t *x11 = (x11_input_t*)data; + + if (!x11) + return false; + + switch (action) + { + case RETRO_SENSOR_ILLUMINANCE_DISABLE: + /* If already disabled, then do nothing */ +#ifdef __linux__ + linux_close_illuminance_sensor(x11->illuminance_sensor); /* noop if NULL */ + x11->illuminance_sensor = NULL; +#endif + case RETRO_SENSOR_GYROSCOPE_DISABLE: + case RETRO_SENSOR_ACCELEROMETER_DISABLE: + /** Unimplemented sensor actions that probably shouldn't fail */ + return true; + +#ifdef __linux__ + case RETRO_SENSOR_ILLUMINANCE_ENABLE: + if (x11->illuminance_sensor) + /* If we already have a sensor, just set the rate */ + linux_set_illuminance_sensor_rate(x11->illuminance_sensor, rate); + else + x11->illuminance_sensor = linux_open_illuminance_sensor(rate); + + return x11->illuminance_sensor != NULL; +#endif + default: + break; + } + + return false; +} + +static float x_get_sensor_input(void *data, unsigned port, unsigned id) +{ + x11_input_t *x11 = (x11_input_t*)data; + + if (!x11) + return 0.0f; + + switch (id) + { +#ifdef __linux__ + case RETRO_SENSOR_ILLUMINANCE: + if (x11->illuminance_sensor) + return linux_get_illuminance_reading(x11->illuminance_sensor); +#endif + default: + break; + } + + return 0.0f; } static void x_input_poll(void *data) @@ -488,7 +556,7 @@ static void x_input_poll(void *data) x11->mouse_m = mask & Button2Mask; x11->mouse_r = mask & Button3Mask; /* Buttons 4 and 5 are not returned here, so they are handled elsewhere. */ - + /* > Mouse pointer */ if (!x11->mouse_grabbed) { @@ -538,13 +606,13 @@ static void x_input_poll(void *data) x11->mouse_y += x11->mouse_delta_y; /* Clamp X */ - if (x11->mouse_x < 0) + if (x11->mouse_x < 0) x11->mouse_x = 0; if (x11->mouse_x >= win_attr.width) x11->mouse_x = (win_attr.width - 1); /* Clamp Y */ - if (x11->mouse_y < 0) + if (x11->mouse_y < 0) x11->mouse_y = 0; if (x11->mouse_y >= win_attr.height) x11->mouse_y = (win_attr.height - 1); @@ -604,8 +672,14 @@ input_driver_t input_x = { x_input_poll, x_input_state, x_input_free, +#ifdef __linux__ + /* Right now this driver only supports the illuminance sensor on Linux. */ + x_set_sensor_state, + x_get_sensor_input, +#else NULL, NULL, +#endif x_input_get_capabilities, "x", x_grab_mouse,