mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
graphics/input: Add getevent input event monitor tool.
Add getevent utility for monitoring input events including mouse clicks/movement, multi-touch coordinates/pressure, and keyboard key presses. Supports automatic device detection in /dev and command-line device path specification with non-blocking I/O. Signed-off-by: liuhongchao <liuhongchao@xiaomi.com>
This commit is contained in:
parent
9db3e26c85
commit
a6286ee0ea
5 changed files with 625 additions and 1 deletions
|
|
@ -62,3 +62,17 @@ if(CONFIG_GRAPHICS_INPUT_TOOL)
|
|||
SRCS
|
||||
generator/input.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_GRAPHICS_INPUT_GETEVENT)
|
||||
nuttx_add_application(
|
||||
MODULE
|
||||
${CONFIG_GRAPHICS_INPUT_GETEVENT}
|
||||
NAME
|
||||
getevent
|
||||
STACKSIZE
|
||||
${CONFIG_GRAPHICS_INPUT_GETEVENT_STACKSIZE}
|
||||
PRIORITY
|
||||
${CONFIG_GRAPHICS_INPUT_GETEVENT_PRIORITY}
|
||||
SRCS
|
||||
getevent/getevent.c)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -59,3 +59,39 @@ config GRAPHICS_INPUT_TOOL_PRIORITY
|
|||
The priority of the input task.
|
||||
|
||||
endif # GRAPHICS_INPUT_TOOL
|
||||
|
||||
menuconfig GRAPHICS_INPUT_GETEVENT
|
||||
tristate "Enable getevent tool"
|
||||
default n
|
||||
---help---
|
||||
The getevent is a command-line input event monitor. It
|
||||
captures and displays real-time events from mouse,
|
||||
touchscreen, and keyboard devices via the input subsystem.
|
||||
|
||||
Use getevent to debug and verify input drivers, inspect
|
||||
raw event data during device bring-up, or diagnose
|
||||
input issues on a running system. It supports automatic
|
||||
device detection in /dev as well as manual device path
|
||||
specification.
|
||||
|
||||
if GRAPHICS_INPUT_GETEVENT
|
||||
|
||||
config GRAPHICS_INPUT_GETEVENT_STACKSIZE
|
||||
int "getevent stack size"
|
||||
default DEFAULT_TASK_STACKSIZE
|
||||
---help---
|
||||
The size of stack allocated for the getevent task.
|
||||
|
||||
config GRAPHICS_INPUT_GETEVENT_PRIORITY
|
||||
int "getevent priority"
|
||||
default 100
|
||||
---help---
|
||||
The priority of the getevent task.
|
||||
|
||||
config GRAPHICS_INPUT_GETEVENT_DETAIL_INFO
|
||||
bool "getevent detail info"
|
||||
default n
|
||||
---help---
|
||||
Enable detailed information in getevent output.
|
||||
|
||||
endif # GRAPHICS_INPUT_GETEVENT
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@
|
|||
#
|
||||
############################################################################
|
||||
|
||||
ifneq ($(CONFIG_GRAPHICS_INPUT_GENERATOR),)
|
||||
ifneq ($(CONFIG_GRAPHICS_INPUT_GENERATOR)$(CONFIG_GRAPHICS_INPUT_GETEVENT),)
|
||||
CONFIGURED_APPS += $(APPDIR)/graphics/input
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -50,4 +50,16 @@ MODULE += $(CONFIG_GRAPHICS_INPUT_TOOL)
|
|||
MAINSRC += generator/input.c
|
||||
endif
|
||||
|
||||
# getevent tool
|
||||
|
||||
ifneq ($(CONFIG_GRAPHICS_INPUT_GETEVENT),)
|
||||
PROGNAME += getevent
|
||||
PRIORITY += $(CONFIG_GRAPHICS_INPUT_GETEVENT_PRIORITY)
|
||||
STACKSIZE += $(CONFIG_GRAPHICS_INPUT_GETEVENT_STACKSIZE)
|
||||
MODULE += $(CONFIG_GRAPHICS_INPUT_GETEVENT)
|
||||
|
||||
MAINSRC += getevent/getevent.c
|
||||
CFLAGS += -Wno-discarded-qualifiers
|
||||
endif
|
||||
|
||||
include $(APPDIR)/Application.mk
|
||||
|
|
|
|||
562
graphics/input/getevent/getevent.c
Normal file
562
graphics/input/getevent/getevent.c
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
/****************************************************************************
|
||||
* apps/graphics/input/getevent/getevent.c
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <nuttx/input/keyboard.h>
|
||||
#include <nuttx/input/mouse.h>
|
||||
#include <nuttx/input/touchscreen.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define GETEVENT_INFO(format, ...) \
|
||||
printf("[getevent]: " format, ##__VA_ARGS__)
|
||||
|
||||
#define GETEVENT_ERR(format, ...) \
|
||||
fprintf(stderr, "[getevent]: " format, ##__VA_ARGS__)
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
struct input_device_s;
|
||||
|
||||
typedef CODE int (*input_init_cb)(FAR struct input_device_s *);
|
||||
typedef CODE void (*input_read_cb)(FAR struct input_device_s *);
|
||||
|
||||
struct touch_priv_s
|
||||
{
|
||||
uint8_t maxpoint;
|
||||
struct touch_sample_s sample[];
|
||||
};
|
||||
|
||||
struct input_device_s
|
||||
{
|
||||
int fd;
|
||||
input_init_cb init_cb;
|
||||
input_read_cb read_cb;
|
||||
FAR void *priv;
|
||||
FAR struct input_device_s *next;
|
||||
char path[];
|
||||
};
|
||||
|
||||
struct input_devices_ctx_s
|
||||
{
|
||||
FAR struct input_device_s *head;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: open_device
|
||||
****************************************************************************/
|
||||
|
||||
static int open_device(FAR struct input_device_s *dev)
|
||||
{
|
||||
GETEVENT_INFO("opening %s\n", dev->path);
|
||||
dev->fd = open(dev->path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
|
||||
if (dev->fd < 0)
|
||||
{
|
||||
GETEVENT_ERR("open %s failed, errno: %d\n", dev->path, errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (dev->init_cb)
|
||||
{
|
||||
return dev->init_cb(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: touch_init
|
||||
****************************************************************************/
|
||||
|
||||
static int touch_init(FAR struct input_device_s *dev)
|
||||
{
|
||||
FAR struct touch_priv_s *priv;
|
||||
uint8_t maxpoint;
|
||||
int ret;
|
||||
|
||||
ret = ioctl(dev->fd, TSIOC_GETMAXPOINTS, &maxpoint);
|
||||
if (ret < 0)
|
||||
{
|
||||
GETEVENT_ERR("ioctl GETMAXPOINTS failed: %d, "
|
||||
"errno: %d\n", ret, errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
priv = calloc(1, sizeof(struct touch_priv_s) +
|
||||
SIZEOF_TOUCH_SAMPLE_S(maxpoint));
|
||||
if (!priv)
|
||||
{
|
||||
GETEVENT_ERR("Memory allocation failed for touch_priv_s\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv->maxpoint = maxpoint;
|
||||
dev->priv = priv;
|
||||
GETEVENT_INFO("ioctl TSIOC_GETMAXPOINTS: %d\n", maxpoint);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: read_mouse_event
|
||||
****************************************************************************/
|
||||
|
||||
static void read_mouse_event(FAR struct input_device_s *dev)
|
||||
{
|
||||
struct mouse_report_s sample;
|
||||
ssize_t nbytes;
|
||||
|
||||
/* Read one sample */
|
||||
|
||||
nbytes = read(dev->fd, &sample, sizeof(struct mouse_report_s));
|
||||
|
||||
/* Print the sample data on successful return */
|
||||
|
||||
if (nbytes == sizeof(struct mouse_report_s))
|
||||
{
|
||||
GETEVENT_INFO("mouse event: %s\n", dev->path);
|
||||
GETEVENT_INFO(" buttons : %02" PRIx8 "\n", sample.buttons);
|
||||
GETEVENT_INFO(" x : %d\n", sample.x);
|
||||
GETEVENT_INFO(" y : %d\n", sample.y);
|
||||
#ifdef CONFIG_INPUT_MOUSE_WHEEL
|
||||
GETEVENT_INFO(" wheel : %d\n", sample.wheel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: read_touch_event
|
||||
****************************************************************************/
|
||||
|
||||
static void read_touch_event(FAR struct input_device_s *dev)
|
||||
{
|
||||
FAR struct touch_priv_s *priv = (FAR struct touch_priv_s *)dev->priv;
|
||||
ssize_t nbytes;
|
||||
size_t i;
|
||||
|
||||
/* Read one sample */
|
||||
|
||||
nbytes = read(dev->fd, priv->sample,
|
||||
SIZEOF_TOUCH_SAMPLE_S(priv->maxpoint));
|
||||
|
||||
/* Print the sample data on successful return */
|
||||
|
||||
if (nbytes == SIZEOF_TOUCH_SAMPLE_S(priv->maxpoint))
|
||||
{
|
||||
GETEVENT_INFO("touch event: %s\n", dev->path);
|
||||
GETEVENT_INFO(" npoints : %" PRId32 "\n", priv->sample->npoints);
|
||||
for (i = 0; i < priv->maxpoint; i++)
|
||||
{
|
||||
if (priv->sample->point[i].flags == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
GETEVENT_INFO("Point : %d\n", priv->sample->point[i].id);
|
||||
GETEVENT_INFO(" flags : %02x\n", priv->sample->point[i].flags);
|
||||
GETEVENT_INFO(" x : %d\n", priv->sample->point[i].x);
|
||||
GETEVENT_INFO(" y : %d\n", priv->sample->point[i].y);
|
||||
#ifdef CONFIG_GRAPHICS_INPUT_GETEVENT_DETAIL_INFO
|
||||
GETEVENT_INFO(" h : %d\n", priv->sample->point[i].h);
|
||||
GETEVENT_INFO(" w : %d\n", priv->sample->point[i].w);
|
||||
GETEVENT_INFO(" pressure : %d\n",
|
||||
priv->sample->point[i].pressure);
|
||||
#endif
|
||||
GETEVENT_INFO(" timestamp : %" PRIu64"\n",
|
||||
priv->sample->point[i].timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: read_keyboard_event
|
||||
****************************************************************************/
|
||||
|
||||
static void read_keyboard_event(FAR struct input_device_s *dev)
|
||||
{
|
||||
struct keyboard_event_s sample;
|
||||
ssize_t nbytes;
|
||||
|
||||
/* Read one sample */
|
||||
|
||||
nbytes = read(dev->fd, &sample, sizeof(struct keyboard_event_s));
|
||||
|
||||
/* Print the sample data on successful return */
|
||||
|
||||
if (nbytes == sizeof(struct keyboard_event_s))
|
||||
{
|
||||
GETEVENT_INFO("keyboard event: %s\n", dev->path);
|
||||
GETEVENT_INFO(" type : %" PRIu32 "\n", sample.type);
|
||||
GETEVENT_INFO(" code : %" PRIu32 "\n", sample.code);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cleanup_input_devices
|
||||
****************************************************************************/
|
||||
|
||||
static void cleanup_input_devices(FAR struct input_devices_ctx_s *ctx)
|
||||
{
|
||||
FAR struct input_device_s *dev = ctx->head;
|
||||
FAR struct input_device_s *next;
|
||||
|
||||
while (dev)
|
||||
{
|
||||
next = dev->next;
|
||||
if (dev->fd >= 0)
|
||||
{
|
||||
close(dev->fd);
|
||||
}
|
||||
|
||||
free(dev->priv);
|
||||
free(dev);
|
||||
dev = next;
|
||||
}
|
||||
|
||||
ctx->head = NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: signal_handler
|
||||
****************************************************************************/
|
||||
|
||||
static void
|
||||
signal_handler(int signum, FAR siginfo_t *info, FAR void *context)
|
||||
{
|
||||
FAR volatile sig_atomic_t *running = info->si_user;
|
||||
*running = 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: run_event_loop
|
||||
****************************************************************************/
|
||||
|
||||
static void run_event_loop(FAR struct input_devices_ctx_s *ctx)
|
||||
{
|
||||
FAR struct input_device_s *dev;
|
||||
FAR struct pollfd *fds = NULL;
|
||||
volatile sig_atomic_t running = 1;
|
||||
struct sigaction sa;
|
||||
int fd_count = 0;
|
||||
int timeout = 500;
|
||||
int i = 0;
|
||||
int ret;
|
||||
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = signal_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_user = (FAR void *)&running;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
||||
for (dev = ctx->head; dev; dev = dev->next)
|
||||
{
|
||||
if (dev->fd >= 0)
|
||||
{
|
||||
fd_count++;
|
||||
}
|
||||
}
|
||||
|
||||
fds = calloc(fd_count, sizeof(struct pollfd));
|
||||
|
||||
if (!fds)
|
||||
{
|
||||
GETEVENT_ERR("Memory allocation failed for fds\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (dev = ctx->head; dev; dev = dev->next)
|
||||
{
|
||||
if (dev->fd >= 0)
|
||||
{
|
||||
fds[i].fd = dev->fd;
|
||||
fds[i].events = POLLIN;
|
||||
fds[i].revents = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
while (running)
|
||||
{
|
||||
ret = poll(fds, fd_count, timeout);
|
||||
if (ret < 0 && errno != EINTR)
|
||||
{
|
||||
GETEVENT_ERR("poll error: %d\n", errno);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < fd_count; i++)
|
||||
{
|
||||
if (!(fds[i].revents & POLLIN))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (dev = ctx->head; dev; dev = dev->next)
|
||||
{
|
||||
if (dev->fd == fds[i].fd)
|
||||
{
|
||||
dev->read_cb(dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(fds);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: show_usage
|
||||
****************************************************************************/
|
||||
|
||||
static void show_usage(void)
|
||||
{
|
||||
printf("Usage: getevent [OPTIONS]\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h Show this help message\n");
|
||||
printf(" -m /dev/mouse -- mouse device path\n");
|
||||
printf(" -t /dev/input -- touch device path\n");
|
||||
printf(" -k /dev/keyboard -- keyboard device path\n");
|
||||
printf("No options: Auto detect devices in /dev\n");
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: alloc_input_device
|
||||
****************************************************************************/
|
||||
|
||||
static FAR struct input_device_s *
|
||||
alloc_input_device(FAR struct input_devices_ctx_s *ctx,
|
||||
FAR const char *path)
|
||||
{
|
||||
FAR struct input_device_s *dev;
|
||||
FAR struct input_device_s *cur;
|
||||
size_t path_len = strlen(path) + 1;
|
||||
|
||||
dev = calloc(1, sizeof(struct input_device_s) + path_len);
|
||||
if (!dev)
|
||||
{
|
||||
GETEVENT_ERR("Memory allocation failed for device\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(dev->path, path, path_len);
|
||||
|
||||
if (!ctx->head)
|
||||
{
|
||||
ctx->head = dev;
|
||||
}
|
||||
else
|
||||
{
|
||||
cur = ctx->head;
|
||||
while (cur->next)
|
||||
{
|
||||
cur = cur->next;
|
||||
}
|
||||
|
||||
cur->next = dev;
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: add_input_device
|
||||
****************************************************************************/
|
||||
|
||||
static int add_input_device(FAR struct input_devices_ctx_s *ctx,
|
||||
input_init_cb init_cb,
|
||||
input_read_cb read_cb,
|
||||
FAR const char *path)
|
||||
{
|
||||
FAR struct input_device_s *dev = alloc_input_device(ctx, path);
|
||||
|
||||
if (!dev)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dev->fd = -1;
|
||||
dev->init_cb = init_cb;
|
||||
dev->read_cb = read_cb;
|
||||
|
||||
return open_device(dev);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: detect_devices
|
||||
****************************************************************************/
|
||||
|
||||
static int detect_devices(FAR struct input_devices_ctx_s *ctx)
|
||||
{
|
||||
FAR struct dirent *entry;
|
||||
char path[PATH_MAX];
|
||||
FAR DIR *dir;
|
||||
int ret = 0;
|
||||
|
||||
dir = opendir("/dev");
|
||||
if (!dir)
|
||||
{
|
||||
GETEVENT_ERR("Failed to open /dev directory, errno: %d\n", errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
while ((entry = readdir(dir)))
|
||||
{
|
||||
if (entry->d_type == DT_CHR)
|
||||
{
|
||||
if (snprintf(path, sizeof(path), "/dev/%s", entry->d_name) < 0)
|
||||
{
|
||||
GETEVENT_ERR("Memory allocation failed for path\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strncmp(entry->d_name, "mouse", 5) == 0)
|
||||
{
|
||||
ret = add_input_device(ctx, NULL, read_mouse_event, path);
|
||||
}
|
||||
else if (strncmp(entry->d_name, "input", 5) == 0)
|
||||
{
|
||||
ret = add_input_device(ctx, touch_init, read_touch_event,
|
||||
path);
|
||||
}
|
||||
else if (strncmp(entry->d_name, "kbd", 3) == 0)
|
||||
{
|
||||
ret = add_input_device(ctx, NULL, read_keyboard_event, path);
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: parse_commandline
|
||||
****************************************************************************/
|
||||
|
||||
static int parse_commandline(FAR struct input_devices_ctx_s *ctx,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
int ch;
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
return detect_devices(ctx);
|
||||
}
|
||||
|
||||
while ((ch = getopt(argc, argv, "hn:m:t:k:")) != -1)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case 'm':
|
||||
ret = add_input_device(ctx, NULL, read_mouse_event, optarg);
|
||||
break;
|
||||
case 't':
|
||||
ret = add_input_device(ctx, touch_init, read_touch_event, optarg);
|
||||
break;
|
||||
case 'k':
|
||||
ret = add_input_device(ctx, NULL, read_keyboard_event, optarg);
|
||||
break;
|
||||
case '?':
|
||||
case 'h':
|
||||
default:
|
||||
show_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: main
|
||||
****************************************************************************/
|
||||
|
||||
int main(int argc, FAR char *argv[])
|
||||
{
|
||||
struct input_devices_ctx_s ctx;
|
||||
int ret;
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
|
||||
/* Parse the command line arguments */
|
||||
|
||||
ret = parse_commandline(&ctx, argc, argv);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Start event loop for input processing */
|
||||
|
||||
run_event_loop(&ctx);
|
||||
|
||||
/* Clean up resources */
|
||||
|
||||
cleanup:
|
||||
cleanup_input_devices(&ctx);
|
||||
|
||||
GETEVENT_INFO("Terminating!\n");
|
||||
|
||||
return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue