support switch-type proximity sensors
Support binary, switch-type proximity sensors. They are are exposed over the input subsystem, emitting the SW_FRONT_PROXIMITY event code. Also provide simple tests, based on the tests for evdev-based accelerometers. Fixes #363
This commit is contained in:
@@ -159,5 +159,6 @@ extern SensorDriver hwmon_light;
|
||||
extern SensorDriver iio_buffer_light;
|
||||
extern SensorDriver iio_buffer_compass;
|
||||
extern SensorDriver iio_poll_proximity;
|
||||
extern SensorDriver input_proximity;
|
||||
|
||||
gboolean drv_check_udev_sensor_type (GUdevDevice *device, const gchar *match, const char *name);
|
||||
|
261
src/drv-input-proximity.c
Normal file
261
src/drv-input-proximity.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2009 Richard Hughes <richard@hughsie.com>
|
||||
* Copyright (C) 2023 Sicelo A. Mhlongo <absicsz@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 3 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "drivers.h"
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#define BITS_PER_LONG (sizeof(long) * 8)
|
||||
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
|
||||
#define OFF(x) ((x)%BITS_PER_LONG)
|
||||
#define BIT(x) (1UL<<OFF(x))
|
||||
#define LONG(x) ((x)/BITS_PER_LONG)
|
||||
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
|
||||
|
||||
typedef struct DrvData {
|
||||
GUdevDevice *dev;
|
||||
gchar *native_path;
|
||||
int last_switch_state;
|
||||
GIOChannel *channel;
|
||||
struct input_event event;
|
||||
gsize offset;
|
||||
guint watcher_id;
|
||||
} DrvData;
|
||||
|
||||
/**
|
||||
* input_str_to_bitmask:
|
||||
* @s: string representation of a hexadecimal value
|
||||
* @bitmask: destination array to store the binary representation of the
|
||||
* input string
|
||||
* @max_size: the size of the allocated bitmask array
|
||||
*
|
||||
* Convert a hexadecimal value represented as a string into its binary
|
||||
* equivalent. The binary value is stored in the bitmask array
|
||||
*
|
||||
* Returns: the number of bits that are set in the resulting bitmask
|
||||
*/
|
||||
static gint
|
||||
input_str_to_bitmask (const gchar *s, glong *bitmask, size_t max_size)
|
||||
{
|
||||
gint i, j;
|
||||
g_auto(GStrv) v = NULL;
|
||||
gint num_bits_set = 0;
|
||||
|
||||
memset (bitmask, 0, max_size);
|
||||
v = g_strsplit (s, " ", max_size);
|
||||
for (i = g_strv_length (v) - 1, j = 0; i >= 0; i--, j++) {
|
||||
gulong val;
|
||||
|
||||
val = strtoul (v[i], NULL, 16);
|
||||
bitmask[j] = val;
|
||||
|
||||
while (val != 0) {
|
||||
num_bits_set++;
|
||||
val &= (val - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return num_bits_set;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
input_device_is_switch (GUdevDevice *device, glong *bitmask)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autofree gchar *native_path = NULL;
|
||||
g_autofree gchar *path;
|
||||
g_autoptr(GError) error = NULL;
|
||||
gint num_bits;
|
||||
|
||||
/* check if the input device has switch capabilities */
|
||||
native_path = g_strdup (g_udev_device_get_sysfs_path (device));
|
||||
path = g_build_filename (native_path, "../capabilities/sw", NULL);
|
||||
if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
|
||||
g_warning_once ("Not a switch [%s]", path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ret = g_file_get_contents (path, &contents, NULL, &error);
|
||||
if (!ret) {
|
||||
g_warning_once ("Failed to read contents of [%s]: %s",
|
||||
path, error->message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* convert attributes of input device into a bitmask */
|
||||
num_bits = input_str_to_bitmask (contents, bitmask, sizeof (bitmask));
|
||||
if ((num_bits == 0) || (num_bits >= SW_CNT)) {
|
||||
g_warning_once ("Invalid bitmask entry for %s", native_path);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
proximity_changed (GIOChannel *channel, GIOCondition condition, gpointer userdata) {
|
||||
g_autoptr(GError) error = NULL;
|
||||
gsize read_bytes;
|
||||
SensorDevice *sensor_device = userdata;
|
||||
DrvData *drv_data = (DrvData *) sensor_device->priv;
|
||||
glong bitmask[NBITS(SW_MAX)];
|
||||
ProximityReadings readings;
|
||||
|
||||
while (g_io_channel_read_chars (channel,
|
||||
((gchar*)&drv_data->event) + drv_data->offset,
|
||||
sizeof(struct input_event) - drv_data->offset,
|
||||
&read_bytes, &error) == G_IO_STATUS_NORMAL) {
|
||||
|
||||
/* check if the event data was completely read */
|
||||
if (drv_data->offset + read_bytes < sizeof (struct input_event)) {
|
||||
drv_data->offset = drv_data->offset + read_bytes;
|
||||
g_warning ("Incomplete data was read");
|
||||
return TRUE;
|
||||
}
|
||||
drv_data->offset = 0;
|
||||
|
||||
if (drv_data->event.type != EV_SW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (drv_data->event.code != SW_FRONT_PROXIMITY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check proximity sensor/switch state */
|
||||
if (ioctl (g_io_channel_unix_get_fd(channel), EVIOCGSW(sizeof (bitmask)), bitmask) < 0) {
|
||||
g_warning ("Could not determine proximity sensor state, ioctl EVIOCGSW failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
drv_data->last_switch_state = test_bit (drv_data->event.code, bitmask);
|
||||
readings.is_near = drv_data->last_switch_state ? PROXIMITY_NEAR_TRUE : PROXIMITY_NEAR_FALSE;
|
||||
sensor_device->callback_func (sensor_device, (gpointer) &readings, sensor_device->user_data);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
watch_input_proximity (gpointer user_data)
|
||||
{
|
||||
SensorDevice *sensor_device = user_data;
|
||||
DrvData *drv_data = (DrvData *) sensor_device->priv;
|
||||
int eventfd;
|
||||
const g_autofree gchar *device_file;
|
||||
g_autoptr(GError) error = NULL;
|
||||
GIOStatus status;
|
||||
glong bitmask[NBITS(SW_MAX)];
|
||||
|
||||
device_file = g_udev_device_get_device_file (drv_data->dev);
|
||||
if (device_file == NULL || device_file[0] == '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
eventfd = open (device_file, O_RDONLY | O_NONBLOCK);
|
||||
if (eventfd < 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* get initial state of sensor */
|
||||
if (ioctl (eventfd, EVIOCGSW(sizeof (bitmask)), bitmask) < 0) {
|
||||
g_warning ("Could not determine sensor state, ioctl EVIOCGSW on %s failed", drv_data->native_path);
|
||||
close (eventfd);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* configure I/O channel and watch it for events */
|
||||
g_debug ("Watching %s (%i)", device_file, eventfd);
|
||||
drv_data->channel = g_io_channel_unix_new (eventfd);
|
||||
g_io_channel_set_close_on_unref (drv_data->channel, TRUE);
|
||||
|
||||
status = g_io_channel_set_encoding (drv_data->channel, NULL, &error);
|
||||
if (status != G_IO_STATUS_NORMAL) {
|
||||
g_warning ("Failed to set encoding for binary data: %s", error->message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
drv_data->watcher_id = g_io_add_watch (drv_data->channel, G_IO_IN, proximity_changed, sensor_device);
|
||||
drv_data->last_switch_state = test_bit (SW_FRONT_PROXIMITY, bitmask);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
input_proximity_discover (GUdevDevice *device)
|
||||
{
|
||||
glong bitmask[NBITS(SW_MAX)];
|
||||
|
||||
if (!input_device_is_switch (device, bitmask))
|
||||
return FALSE;
|
||||
|
||||
/* is this SW_FRONT_PROXIMITY? */
|
||||
if (!test_bit (SW_FRONT_PROXIMITY, bitmask))
|
||||
return FALSE;
|
||||
|
||||
/* Input proximity sensor found */
|
||||
g_debug ("Found input proximity sensor at %s",
|
||||
g_udev_device_get_sysfs_path (device));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
input_proximity_set_polling (SensorDevice *sensor_device, gboolean state)
|
||||
{
|
||||
DrvData *drv_data = (DrvData *) sensor_device->priv;
|
||||
|
||||
if (drv_data->watcher_id > 0 && state)
|
||||
return;
|
||||
|
||||
if (drv_data->watcher_id == 0 && !state)
|
||||
return;
|
||||
|
||||
g_clear_handle_id (&drv_data->watcher_id, g_source_remove);
|
||||
if (state) {
|
||||
/* start watching for proximity events */
|
||||
watch_input_proximity (sensor_device);
|
||||
}
|
||||
}
|
||||
|
||||
static SensorDevice *
|
||||
input_proximity_open (GUdevDevice *device)
|
||||
{
|
||||
SensorDevice *sensor_device;
|
||||
DrvData *drv_data;
|
||||
|
||||
sensor_device = g_new0 (SensorDevice, 1);
|
||||
sensor_device->name = g_strdup (g_udev_device_get_name (device));
|
||||
sensor_device->priv = g_new0 (DrvData, 1);
|
||||
drv_data = (DrvData *) sensor_device->priv;
|
||||
|
||||
drv_data->dev = g_object_ref (device);
|
||||
drv_data->native_path = g_strdup (g_udev_device_get_sysfs_path (device));
|
||||
|
||||
return sensor_device;
|
||||
}
|
||||
|
||||
static void
|
||||
input_proximity_close (SensorDevice *sensor_device)
|
||||
{
|
||||
DrvData *drv_data = (DrvData *) sensor_device->priv;
|
||||
|
||||
g_clear_object (&drv_data->dev);
|
||||
g_clear_pointer (&sensor_device->priv, g_free);
|
||||
g_free (sensor_device);
|
||||
}
|
||||
|
||||
SensorDriver input_proximity = {
|
||||
.driver_name = "Input proximity",
|
||||
.type = DRIVER_TYPE_PROXIMITY,
|
||||
.discover = input_proximity_discover,
|
||||
.open = input_proximity_open,
|
||||
.close = input_proximity_close,
|
||||
.set_polling = input_proximity_set_polling,
|
||||
};
|
@@ -74,6 +74,7 @@ static const SensorDriver * const drivers[] = {
|
||||
&fake_light,
|
||||
&iio_buffer_compass,
|
||||
&iio_poll_proximity,
|
||||
&input_proximity,
|
||||
};
|
||||
|
||||
static ReadingsUpdateFunc driver_type_to_callback_func (DriverType type);
|
||||
|
@@ -28,6 +28,7 @@ sources = [
|
||||
'drv-iio-buffer-light.c',
|
||||
'drv-iio-buffer-compass.c',
|
||||
'drv-iio-poll-proximity.c',
|
||||
'drv-input-proximity.c',
|
||||
'iio-buffer-utils.c',
|
||||
'accel-mount-matrix.c',
|
||||
'accel-scale.c',
|
||||
|
85
tests/input-proximity-data/device
Normal file
85
tests/input-proximity-data/device
Normal file
@@ -0,0 +1,85 @@
|
||||
P: /devices/platform/gpio_keys/input/input4/event4
|
||||
N: input/event4
|
||||
S: input/by-path/platform-gpio_keys-event
|
||||
E: DEVLINKS=/dev/input/by-path/platform-gpio_keys-event
|
||||
E: DEVNAME=/dev/input/event4
|
||||
E: ID_INPUT=1
|
||||
E: ID_INPUT_KEY=1
|
||||
E: ID_INPUT_SWITCH=1
|
||||
E: ID_PATH=platform-gpio_keys
|
||||
E: ID_PATH_TAG=platform-gpio_keys
|
||||
E: LIBINPUT_DEVICE_GROUP=19/1/1:gpio-keys
|
||||
E: MAJOR=13
|
||||
E: MINOR=68
|
||||
E: SUBSYSTEM=input
|
||||
E: TAGS=:power-switch:
|
||||
A: dev=13:68\n
|
||||
L: device=../../input4
|
||||
A: power/control=auto\n
|
||||
A: power/runtime_active_time=0\n
|
||||
A: power/runtime_status=unsupported\n
|
||||
A: power/runtime_suspended_time=0\n
|
||||
|
||||
P: /devices/platform/gpio_keys/input/input4
|
||||
E: EV=23
|
||||
E: ID_FOR_SEAT=input-platform-gpio_keys
|
||||
E: ID_INPUT=1
|
||||
E: ID_INPUT_KEY=1
|
||||
E: ID_INPUT_SWITCH=1
|
||||
E: ID_PATH=platform-gpio_keys
|
||||
E: ID_PATH_TAG=platform-gpio_keys
|
||||
E: KEY=10000 0 0 0 0 0 0 0 0 0 100000 0 1000000 0 0 0 0
|
||||
E: MODALIAS=input:b0019v0001p0001e0100-e0,1,5,k98,D4,210,ramlsfw9,A,B,
|
||||
E: NAME="gpio_keys"
|
||||
E: PHYS="gpio-keys/input0"
|
||||
E: PRODUCT=19/1/1/100
|
||||
E: PROP=0
|
||||
E: SUBSYSTEM=input
|
||||
E: SW=10e00
|
||||
E: TAGS=:seat:
|
||||
A: capabilities/abs=0\n
|
||||
A: capabilities/ev=23\n
|
||||
A: capabilities/ff=0\n
|
||||
A: capabilities/key=10000 0 0 0 0 0 0 0 0 0 100000 0 1000000 0 0 0 0\n
|
||||
A: capabilities/led=0\n
|
||||
A: capabilities/msc=0\n
|
||||
A: capabilities/rel=0\n
|
||||
A: capabilities/snd=0\n
|
||||
A: capabilities/sw=10e00\n
|
||||
L: device=../../../gpio_keys
|
||||
A: id/bustype=0019\n
|
||||
A: id/product=0001\n
|
||||
A: id/vendor=0001\n
|
||||
A: id/version=0100\n
|
||||
A: inhibited=0\n
|
||||
A: modalias=input:b0019v0001p0001e0100-e0,1,5,k98,D4,210,ramlsfw9,A,B,\n
|
||||
A: name=gpio_keys\n
|
||||
A: phys=gpio-keys/input0\n
|
||||
A: power/control=auto\n
|
||||
A: power/runtime_active_time=0\n
|
||||
A: power/runtime_status=unsupported\n
|
||||
A: power/runtime_suspended_time=0\n
|
||||
A: properties=0\n
|
||||
A: uniq=\n
|
||||
|
||||
P: /devices/platform/gpio_keys
|
||||
E: DRIVER=gpio-keys
|
||||
E: MODALIAS=of:Ngpio_keysT(null)Cgpio-keys
|
||||
E: OF_COMPATIBLE_0=gpio-keys
|
||||
E: OF_COMPATIBLE_N=1
|
||||
E: OF_FULLNAME=/gpio_keys
|
||||
E: OF_NAME=gpio_keys
|
||||
E: SUBSYSTEM=platform
|
||||
A: disabled_keys=\n
|
||||
A: disabled_switches=\n
|
||||
L: driver=../../../bus/platform/drivers/gpio-keys
|
||||
A: driver_override=(null)\n
|
||||
A: keys=152,212,528\n
|
||||
A: modalias=of:Ngpio_keysT(null)Cgpio-keys\n
|
||||
L: of_node=../../../firmware/devicetree/base/gpio_keys
|
||||
A: power/control=auto\n
|
||||
A: power/runtime_active_time=0\n
|
||||
A: power/runtime_status=unsupported\n
|
||||
A: power/runtime_suspended_time=0\n
|
||||
A: switches=9-11,16\n
|
||||
|
3
tests/input-proximity-data/ioctl
Normal file
3
tests/input-proximity-data/ioctl
Normal file
@@ -0,0 +1,3 @@
|
||||
@DEV /dev/input/event4
|
||||
EVIOCGSW(0) 4 00020100
|
||||
EVIOCGSW(0) 4 000A0100
|
@@ -457,6 +457,20 @@ class Tests(dbusmock.DBusTestCase):
|
||||
|
||||
self.stop_daemon()
|
||||
|
||||
def test_input_proximity(self):
|
||||
'''input proximity'''
|
||||
top_srcdir = os.getenv('top_srcdir', '.')
|
||||
script = ['umockdev-run', '-d', top_srcdir + '/tests/input-proximity-data/device',
|
||||
'-i', '/dev/input/event4=%s/tests/input-proximity-data/ioctl' % (top_srcdir),
|
||||
'--']
|
||||
|
||||
self.start_daemon(wrapper=script)
|
||||
|
||||
self.assertEqual(self.get_dbus_property('HasProximity'), True)
|
||||
self.assertEqual(self.get_dbus_property('ProximityNear'), False)
|
||||
|
||||
self.stop_daemon()
|
||||
|
||||
def test_iio_buffer_accel(self):
|
||||
'''iio buffer accel'''
|
||||
top_srcdir = os.getenv('top_srcdir', '.')
|
||||
|
Reference in New Issue
Block a user