frecon: add support for multiple input devices
Also, report user activity to power_manager via dbus, so it wouldn't blank
the screen. Also enumerate input devices and handle hotplug/unplug events
using udev.
BUG=chromium:406039
TEST=emerge, deploy and run frecon, connect and disconnect USB keyboard
Change-Id: Ia536a066439fa79decb9c2aa3f2ff64e2d6447bd
Signed-off-by: Dominik Behr <dbehr@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/213216
Reviewed-by: Stéphane Marchesin <marcheu@chromium.org>
diff --git a/Makefile b/Makefile
index cc5b8a3..f2b0d57 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
include common.mk
-PC_DEPS = libdrm libtsm
+PC_DEPS = libdrm libtsm libudev dbus-1
PC_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PC_DEPS))
PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))
diff --git a/input.c b/input.c
index 8d5d816..f7c4a1f 100644
--- a/input.c
+++ b/input.c
@@ -11,59 +11,280 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-
+#include <sys/select.h>
+#include <errno.h>
+#include <libudev.h>
+#include <dbus/dbus.h>
#include "input.h"
+#include "mini_power_manager.h"
-static int fd;
+struct input_dev {
+ int fd;
+ char *path;
+};
-int input_init()
+struct {
+ struct udev *udev;
+ struct udev_monitor *udev_monitor;
+ int udev_fd;
+ unsigned int ndevs;
+ struct input_dev *devs;
+ DBusConnection *dbus_conn;
+} input = {
+ .udev = NULL,
+ .udev_monitor = NULL,
+ .udev_fd = -1,
+ .ndevs = 0,
+ .devs = NULL,
+ .dbus_conn = NULL
+};
+
+static int input_add(const char *devname)
{
- int ret;
-
- /* XXX open all evdev devices and poll all the FDs */
- fd = open("/dev/input/event0", O_RDONLY);
+ int ret = 0, fd = -1;
+ /* for some reason every device has a null enumerations and notifications
+ of every device come with NULL string first */
+ if (!devname) {
+ ret = -EINVAL;
+ goto errorret;
+ }
+ ret = fd = open(devname, O_RDONLY);
if (fd < 0)
- return fd;
+ goto errorret;
- if (!isatty(fileno(stdout)))
- setbuf(stdout, NULL);
-
- /* Check for grabs. */
ret = ioctl(fd, EVIOCGRAB, (void *) 1);
if (!ret) {
ioctl(fd, EVIOCGRAB, (void *) 0);
} else {
- printf("Evdev device grabbed by another process\n");
- close(fd);
- return -1;
+ fprintf(stderr, "Evdev device %s grabbed by another process\n",
+ devname);
+ ret = -EBUSY;
+ goto closefd;
}
+ struct input_dev *newdevs =
+ realloc(input.devs, (input.ndevs + 1) * sizeof (struct input_dev));
+ if (!newdevs) {
+ ret = -ENOMEM;
+ goto closefd;
+ }
+ input.devs = newdevs;
+ input.devs[input.ndevs].fd = fd;
+ input.devs[input.ndevs].path = strdup(devname);
+ if (!input.devs[input.ndevs].path) {
+ ret = -ENOMEM;
+ goto closefd;
+ }
+ input.ndevs++;
+
+ return fd;
+
+closefd:
+ close(fd);
+errorret:
+ return ret;
+}
+
+static void input_remove(const char *devname)
+{
+ if (!devname) {
+ return;
+ }
+ unsigned int u;
+ for (u = 0; u < input.ndevs; u++) {
+ if (!strcmp(devname, input.devs[u].path)) {
+ free(input.devs[u].path);
+ close(input.devs[u].fd);
+ input.ndevs--;
+ if (u != input.ndevs) {
+ input.devs[u] = input.devs[input.ndevs];
+ }
+ return;
+ }
+ }
+}
+
+static bool check_dbus_error(DBusError * err, const char *msg)
+{
+ if (dbus_error_is_set(err)) {
+ fprintf(stderr, "%s name:%s message:%s\n", msg, err->name,
+ err->message);
+ return true;
+ }
+ return false;
+}
+
+int input_init()
+{
+ input.udev = udev_new();
+ if (!input.udev)
+ return -ENOENT;
+ input.udev_monitor = udev_monitor_new_from_netlink(input.udev, "udev");
+ if (!input.udev_monitor) {
+ udev_unref(input.udev);
+ return -ENOENT;
+ }
+ udev_monitor_filter_add_match_subsystem_devtype(input.udev_monitor, "input",
+ NULL);
+ udev_monitor_enable_receiving(input.udev_monitor);
+ input.udev_fd = udev_monitor_get_fd(input.udev_monitor);
+
+ struct udev_enumerate *udev_enum;
+ struct udev_list_entry *devices, *deventry;
+ udev_enum = udev_enumerate_new(input.udev);
+ udev_enumerate_add_match_subsystem(udev_enum, "input");
+ udev_enumerate_scan_devices(udev_enum);
+ devices = udev_enumerate_get_list_entry(udev_enum);
+ udev_list_entry_foreach(deventry, devices) {
+ const char *syspath;
+ struct udev_device *dev;
+ syspath = udev_list_entry_get_name(deventry);
+ dev = udev_device_new_from_syspath(input.udev, syspath);
+ input_add(udev_device_get_devnode(dev));
+ udev_device_unref(dev);
+ }
+ udev_enumerate_unref(udev_enum);
+
+ if (!isatty(fileno(stdout)))
+ setbuf(stdout, NULL);
+
return 0;
}
void input_close()
{
- close(fd);
+ unsigned int u;
+ for (u = 0; u < input.ndevs; u++) {
+ free(input.devs[u].path);
+ close(input.devs[u].fd);
+ }
+ free(input.devs);
+ input.devs = NULL;
+ input.ndevs = 0;
+
+ udev_monitor_unref(input.udev_monitor);
+ input.udev_monitor = NULL;
+ udev_unref(input.udev);
+ input.udev = NULL;
+ input.udev_fd = -1;
+
+ if (input.dbus_conn) {
+ /* FIXME - not sure what the right counterpart to
+ dbus_bus_get() is, unref documentation is rather
+ unclear. Not a big issue but it would be nice to
+ clean up properly here */
+ /* dbus_connection_unref(input.dbus_conn); */
+ input.dbus_conn = NULL;
+ }
}
-struct input_key_event *input_get_event()
+int input_setfds(fd_set * read_set, fd_set * exception_set)
{
+ unsigned int u;
+ int max = -1;
+ for (u = 0; u < input.ndevs; u++) {
+ FD_SET(input.devs[u].fd, read_set);
+ FD_SET(input.devs[u].fd, exception_set);
+ if (input.devs[u].fd > max)
+ max = input.devs[u].fd;
+ }
+
+ FD_SET(input.udev_fd, read_set);
+ FD_SET(input.udev_fd, exception_set);
+ if (input.udev_fd > max)
+ max = input.udev_fd;
+ return max;
+}
+
+static void report_user_activity(void)
+{
+ DBusError err;
+ dbus_error_init(&err);
+
+ if (!input.dbus_conn) {
+ input.dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+ if (check_dbus_error(&err, "Cannot get dbus connection"))
+ return;
+ dbus_connection_set_exit_on_disconnect(input.dbus_conn, FALSE);
+ }
+
+ if (!dbus_bus_name_has_owner(input.dbus_conn, kPowerManagerServiceName, &err)) {
+ fprintf(stderr, "Power_manager not available on dbus!\n");
+ return;
+ }
+
+ DBusMessage *msg = NULL;
+ unsigned int activity_type = USER_ACTIVITY_OTHER;
+
+ msg = dbus_message_new_method_call(kPowerManagerServiceName,
+ kPowerManagerServicePath,
+ kPowerManagerInterface,
+ kHandleUserActivityMethod);
+ if (!msg)
+ return;
+ dbus_message_set_no_reply(msg, TRUE);
+ if (!dbus_message_append_args(msg,
+ DBUS_TYPE_UINT32, &activity_type,
+ DBUS_TYPE_INVALID)) {
+ dbus_message_unref(msg);
+ return;
+ }
+ if (!dbus_connection_send(input.dbus_conn, msg, NULL)) {
+ }
+ dbus_connection_flush(input.dbus_conn);
+ dbus_message_unref(msg);
+}
+
+struct input_key_event *input_get_event(fd_set * read_set,
+ fd_set * exception_set)
+{
+ unsigned int u;
struct input_event ev;
int ret;
- ret = read(fd, &ev, sizeof (struct input_event));
- if (ret < (int) sizeof (struct input_event)) {
- printf("expected %d bytes, got %d\n",
- (int) sizeof (struct input_event), ret);
- return NULL;
+ if (FD_ISSET(input.udev_fd, exception_set)) {
+ /* udev died on us? */
+ fprintf(stderr, "Exception on udev fd\n");
}
- if (ev.type == EV_KEY) {
- struct input_key_event *event = malloc(sizeof (*event));
- event->code = ev.code;
- event->value = ev.value;
- return event;
+ if (FD_ISSET(input.udev_fd, read_set)
+ && !FD_ISSET(input.udev_fd, exception_set)) {
+ /* we got an udev notification */
+ struct udev_device *dev =
+ udev_monitor_receive_device(input.udev_monitor);
+ if (dev) {
+ if (!strcmp("add", udev_device_get_action(dev))) {
+ input_add(udev_device_get_devnode(dev));
+ } else
+ if (!strcmp("remove", udev_device_get_action(dev)))
+ {
+ input_remove(udev_device_get_devnode(dev));
+ }
+ udev_device_unref(dev);
+ }
+ }
+
+ for (u = 0; u < input.ndevs; u++) {
+ if (FD_ISSET(input.devs[u].fd, read_set)
+ && !FD_ISSET(input.devs[u].fd, exception_set)) {
+ ret =
+ read(input.devs[u].fd, &ev, sizeof (struct input_event));
+ if (ret < (int) sizeof (struct input_event)) {
+ printf("expected %d bytes, got %d\n",
+ (int) sizeof (struct input_event), ret);
+ return NULL;
+ }
+
+ if (ev.type == EV_KEY) {
+ struct input_key_event *event =
+ malloc(sizeof (*event));
+ event->code = ev.code;
+ event->value = ev.value;
+ report_user_activity();
+ return event;
+ }
+ }
}
return NULL;
@@ -73,8 +294,3 @@
{
free(event);
}
-
-int input_get_fd()
-{
- return fd;
-}
diff --git a/input.h b/input.h
index ce71ab6..d44f945 100644
--- a/input.h
+++ b/input.h
@@ -16,8 +16,8 @@
int input_init();
void input_close();
-struct input_key_event *input_get_event();
+int input_setfds(fd_set *read_set, fd_set *exception_set);
+struct input_key_event *input_get_event(fd_set *read_fds, fd_set *exception_set);
void input_put_event(struct input_key_event *event);
-int input_get_fd();
#endif
diff --git a/mini_power_manager.h b/mini_power_manager.h
new file mode 100644
index 0000000..f03a4b3
--- /dev/null
+++ b/mini_power_manager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef MINI_POWER_MANAGER_H
+#define MINI_POWER_MANAGER_H
+/* Minimal set of power manager constants copied from
+ platform/system_api/dbus/service_constants.h which are C++
+ header file so we can't use it in our code directly */
+
+const char kPowerManagerInterface[] = "org.chromium.PowerManager";
+const char kPowerManagerServicePath[] = "/org/chromium/PowerManager";
+const char kPowerManagerServiceName[] = "org.chromium.PowerManager";
+/* Methods exposed by powerd. */
+const char kDecreaseScreenBrightnessMethod[] = "DecreaseScreenBrightness";
+const char kIncreaseScreenBrightnessMethod[] = "IncreaseScreenBrightness";
+const char kHandleUserActivityMethod[] = "HandleUserActivity";
+/* Values */
+const int kBrightnessTransitionGradual = 1;
+const int kBrightnessTransitionInstant = 2;
+enum UserActivityType {
+ USER_ACTIVITY_OTHER = 0,
+ USER_ACTIVITY_BRIGHTNESS_UP_KEY_PRESS = 1,
+ USER_ACTIVITY_BRIGHTNESS_DOWN_KEY_PRESS = 2,
+ USER_ACTIVITY_VOLUME_UP_KEY_PRESS = 3,
+ USER_ACTIVITY_VOLUME_DOWN_KEY_PRESS = 4,
+ USER_ACTIVITY_VOLUME_MUTE_KEY_PRESS = 5,
+};
+
+#endif /* MINI_POWER_MANAGER_H */
+
diff --git a/term.c b/term.c
index f2d928b..8e1a6ca 100644
--- a/term.c
+++ b/term.c
@@ -8,6 +8,7 @@
#include <libtsm.h>
#include <paths.h>
#include <stdio.h>
+#include <sys/select.h>
#include "font.h"
#include "input.h"
@@ -227,40 +228,35 @@
int term_run()
{
- int input_fd = input_get_fd();
int pty_fd = term.pty_bridge;
fd_set read_set, exception_set;
while (1) {
FD_ZERO(&read_set);
- FD_SET(input_fd, &read_set);
- FD_SET(pty_fd, &read_set);
FD_ZERO(&exception_set);
- FD_SET(input_fd, &exception_set);
+ FD_SET(pty_fd, &read_set);
FD_SET(pty_fd, &exception_set);
+ int maxfd = input_setfds(&read_set, &exception_set);
- int maxfd = MAX(input_fd, pty_fd) + 1;
+ maxfd = MAX(maxfd, pty_fd) + 1;
select(maxfd, &read_set, NULL, &exception_set, NULL);
- if (FD_ISSET(input_fd, &exception_set)
- || FD_ISSET(pty_fd, &exception_set))
+ if (FD_ISSET(pty_fd, &exception_set))
return -1;
- if (FD_ISSET(input_fd, &read_set)) {
- struct input_key_event *event;
- event = input_get_event();
- if (event) {
- if (!term_special_key(event) && event->value) {
- uint32_t keysym, unicode;
- term_get_keysym_and_unicode(event,
- &keysym,
- &unicode);
- term_key_event(keysym, unicode);
- }
-
- input_put_event(event);
+ struct input_key_event *event;
+ event = input_get_event(&read_set, &exception_set);
+ if (event) {
+ if (!term_special_key(event) && event->value) {
+ uint32_t keysym, unicode;
+ term_get_keysym_and_unicode(event,
+ &keysym,
+ &unicode);
+ term_key_event(keysym, unicode);
}
+
+ input_put_event(event);
}
if (FD_ISSET(pty_fd, &read_set)) {