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)) {