Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Linux support for illuminance sensors #17208

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
256 changes: 255 additions & 1 deletion input/common/linux_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,57 @@
* If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <signal.h>
#include <sys/types.h>

#include <dirent.h>
#include <linux/input.h>
#include <linux/kd.h>
#include <termios.h>
#include <unistd.h>

#include "linux_common.h"
#include "verbosity.h"
#include <retro_assert.h>
#include <retro_timers.h>
#include <rthreads/rthreads.h>

/* We can assume that pthreads are available on Linux. */
#include <pthread.h>
#include <string.h>

#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)
Expand Down Expand Up @@ -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;
}
21 changes: 21 additions & 0 deletions input/common/linux_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading