int val[];
};
+/**
+ * Internal only: log data used to send messages to the respective log
+ * handler. We re-use the same struct for a global and inside
+ * struct libevdev.
+ * For the global, device_handler is NULL, for per-device instance
+ * global_handler is NULL.
+ */
+struct logdata {
+ enum libevdev_log_priority priority; /** minimum logging priority */
+ libevdev_log_func_t global_handler; /** global handler function */
+ libevdev_device_log_func_t device_handler; /** per-device handler function */
+ void *userdata; /** user-defined data pointer */
+};
+
struct libevdev {
int fd;
bool initialized;
unsigned long *tracking_id_changes;
size_t tracking_id_changes_sz; /* in bytes */
} mt_sync;
-};
-struct logdata {
- enum libevdev_log_priority priority; /** minimum logging priority */
- libevdev_log_func_t handler; /** handler function */
- void *userdata; /** user-defined data pointer */
+ struct logdata log;
};
-extern struct logdata log_data;
-#define log_msg_cond(priority, ...) \
+#define log_msg_cond(dev, priority, ...) \
do { \
- if (libevdev_get_log_priority() >= priority) \
- log_msg(priority, log_data.userdata, __FILE__, __LINE__, __func__, __VA_ARGS__); \
+ if (log_priority(dev) >= priority) \
+ log_msg(dev, priority, __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while(0)
-#define log_error(...) log_msg_cond(LIBEVDEV_LOG_ERROR, __VA_ARGS__)
-#define log_info(...) log_msg_cond(LIBEVDEV_LOG_INFO, __VA_ARGS__)
-#define log_dbg(...) log_msg_cond(LIBEVDEV_LOG_DEBUG, __VA_ARGS__)
-#define log_bug(...) log_msg_cond(LIBEVDEV_LOG_ERROR, "BUG: "__VA_ARGS__)
+#define log_error(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, __VA_ARGS__)
+#define log_info(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_INFO, __VA_ARGS__)
+#define log_dbg(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_DEBUG, __VA_ARGS__)
+#define log_bug(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, "BUG: "__VA_ARGS__)
extern void
-log_msg(enum libevdev_log_priority priority,
- void *data,
+log_msg(const struct libevdev *dev,
+ enum libevdev_log_priority priority,
const char *file, int line, const char *func,
const char *format, ...) LIBEVDEV_ATTRIBUTE_PRINTF(6, 7);
+extern enum libevdev_log_priority
+log_priority(const struct libevdev *dev);
/**
* @return a pointer to the next element in the queue, or NULL if the queue
if (strcmp(buf, uinput_dev->name) == 0) {
if (uinput_dev->syspath) {
/* FIXME: could descend into bit comparison here */
- log_info("multiple identical devices found. syspath is unreliable\n");
+ log_info(NULL, "multiple identical devices found. syspath is unreliable\n");
break;
} else {
strcpy(buf, SYS_INPUT_DIR);
new_device->fd_is_managed = 1;
} else if (fd < 0) {
- log_bug("Invalid fd %d\n", fd);
+ log_bug(NULL, "Invalid fd %d\n", fd);
errno = EBADF;
goto error;
}
new_device->fd = fd;
if (fetch_syspath_and_devnode(new_device) == -1) {
- log_error("unable to fetch syspath or device node.\n");
+ log_error(NULL, "unable to fetch syspath or device node.\n");
errno = ENODEV;
goto error;
}
slot_value(const struct libevdev *dev, int slot, int axis)
{
if (unlikely(slot > dev->num_slots)) {
- log_bug("Slot %d exceeds number of slots (%d)\n", slot, dev->num_slots);
+ log_bug(dev, "Slot %d exceeds number of slots (%d)\n", slot, dev->num_slots);
slot = 0;
}
if (unlikely(axis < ABS_MT_MIN || axis > ABS_MT_MAX)) {
- log_bug("MT axis %d is outside the valid range [%d,%d]\n",
+ log_bug(dev, "MT axis %d is outside the valid range [%d,%d]\n",
axis, ABS_MT_MIN, ABS_MT_MAX);
axis = ABS_MT_MIN;
}
/*
* Global logging settings.
*/
-struct logdata log_data = {
- LIBEVDEV_LOG_INFO,
- libevdev_dflt_log_func,
- NULL,
+static struct logdata log_data = {
+ .priority = LIBEVDEV_LOG_INFO,
+ .global_handler = libevdev_dflt_log_func,
+ .userdata = NULL,
};
void
-log_msg(enum libevdev_log_priority priority,
- void *data,
+log_msg(const struct libevdev *dev,
+ enum libevdev_log_priority priority,
const char *file, int line, const char *func,
const char *format, ...)
{
va_list args;
- if (!log_data.handler || priority > log_data.priority)
+ if (dev && dev->log.device_handler) {
+ /**
+ * if a global handler is set for a device does
+ * we've set up the handlers wrong. And that means we'll
+ * likely get the printf args wrong and cause all sorts of
+ * mayhem. Seppuku is called for.
+ */
+ if (unlikely(dev->log.global_handler))
+ abort();
+
+ if (priority > dev->log.priority)
+ return;
+ } else if (!log_data.global_handler || priority > log_data.priority)
return;
+ else if (unlikely(log_data.device_handler))
+ abort(); /* Seppuku, see above */
+
va_start(args, format);
- log_data.handler(priority, data, file, line, func, format, args);
+ if (dev && dev->log.device_handler)
+ dev->log.device_handler(dev, priority, dev->log.userdata, file, line, func, format, args);
+ else
+ log_data.global_handler(priority, log_data.userdata, file, line, func, format, args);
va_end(args);
}
static void
libevdev_reset(struct libevdev *dev)
{
+ enum libevdev_log_priority pri = dev->log.priority;
+ libevdev_device_log_func_t handler = dev->log.device_handler;
+
free(dev->name);
free(dev->phys);
free(dev->uniq);
dev->current_slot = -1;
dev->grabbed = LIBEVDEV_UNGRAB;
dev->sync_state = SYNC_NONE;
+ dev->log.priority = pri;
+ dev->log.device_handler = handler;
libevdev_enable_event_type(dev, EV_SYN);
}
LIBEVDEV_EXPORT void
libevdev_set_log_function(libevdev_log_func_t logfunc, void *data)
{
- log_data.handler = logfunc;
+ log_data.global_handler = logfunc;
log_data.userdata = data;
}
return log_data.priority;
}
+LIBEVDEV_EXPORT void
+libevdev_set_device_log_function(struct libevdev *dev,
+ libevdev_device_log_func_t logfunc,
+ enum libevdev_log_priority priority,
+ void *data)
+{
+ if (!dev) {
+ log_bug(NULL, "device must not be NULL\n");
+ return;
+ }
+
+ dev->log.priority = priority;
+ dev->log.device_handler = logfunc;
+ dev->log.userdata = data;
+}
+
+enum libevdev_log_priority
+log_priority(const struct libevdev *dev)
+{
+ if (dev && dev->log.device_handler)
+ return dev->log.priority;
+ else
+ return libevdev_get_log_priority();
+}
+
LIBEVDEV_EXPORT int
libevdev_change_fd(struct libevdev *dev, int fd)
{
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -1;
}
dev->fd = fd;
char buf[256];
if (dev->initialized) {
- log_bug("device already initialized.\n");
+ log_bug(dev, "device already initialized.\n");
return -EBADF;
} else if (fd < 0)
return -EBADF;
return;
if (rc < 0) {
- log_error("Failed to drain events before sync.\n");
+ log_error(dev, "Failed to drain events before sync.\n");
return;
}
we can drain them.
*/
if (iterations >= max_iterations)
- log_info("Unable to drain events, buffer size mismatch.\n");
+ log_info(dev, "Unable to drain events, buffer size mismatch.\n");
}
static int
if (unlikely(dev->num_slots > -1 &&
libevdev_event_is_code(ev, EV_ABS, ABS_MT_SLOT) &&
(ev->value < 0 || ev->value >= dev->num_slots))) {
- log_bug("Device \"%s\" received an invalid slot index %d."
+ log_bug(dev, "Device \"%s\" received an invalid slot index %d."
"Capping to announced max slot number %d.\n",
dev->name, ev->value, dev->num_slots - 1);
ev->value = dev->num_slots - 1;
*slot_value(dev, dev->current_slot, ABS_MT_TRACKING_ID) == -1) ||
(ev->value != -1 &&
*slot_value(dev, dev->current_slot, ABS_MT_TRACKING_ID) != -1)))) {
- log_bug("Device \"%s\" received a double tracking ID %d in slot %d.\n",
+ log_bug(dev, "Device \"%s\" received a double tracking ID %d in slot %d.\n",
dev->name, ev->value, dev->current_slot);
return EVENT_FILTER_DISCARD;
}
enum event_filter_status filter_status;
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
if (!(flags & (LIBEVDEV_READ_FLAG_NORMAL|LIBEVDEV_READ_FLAG_SYNC|LIBEVDEV_READ_FLAG_FORCE_SYNC))) {
- log_bug("invalid flags %#x\n.\n", flags);
+ log_bug(dev, "invalid flags %#x\n.\n", flags);
return -EINVAL;
}
if (queue_peek(dev, 0, &next) == 0 &&
next.type == EV_SYN && next.code == SYN_DROPPED)
- log_info("SYN_DROPPED received after finished "
+ log_info(dev, "SYN_DROPPED received after finished "
"sync - you're not keeping up\n");
}
}
int rc;
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
int rc;
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
int rc = 0;
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
if (grab != LIBEVDEV_GRAB && grab != LIBEVDEV_UNGRAB) {
- log_bug("invalid grab parameter %#x\n", grab);
+ log_bug(dev, "invalid grab parameter %#x\n", grab);
return -EINVAL;
}
size_t nleds = 0;
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
libevdev_set_clock_id(struct libevdev *dev, int clockid)
{
if (!dev->initialized) {
- log_bug("device not initialized. call libevdev_set_fd() first\n");
+ log_bug(dev, "device not initialized. call libevdev_set_fd() first\n");
return -EBADF;
} else if (dev->fd < 0)
return -EBADF;
* before libevdev_set_fd() unless documented otherwise.
*/
+/**
+ * @defgroup logging Library logging facilities
+ *
+ * libevdev provides two methods of logging library-internal messages. The
+ * old method is to provide a global log handler in
+ * libevdev_set_log_function(). The new method is to provide a per-context
+ * log handler in libevdev_set_device_log_function(). Developers are encouraged
+ * to use the per-context logging facilities over the global log handler as
+ * it provides access to the libevdev instance that caused a message, and is
+ * more flexible when libevdev is used from within a shared library.
+ *
+ * If a caller sets both the global log handler and a per-context log
+ * handler, each device with a per-context log handler will only invoke that
+ * log handler.
+ *
+ * @note To set a context-specific log handler, a context is needed.
+ * Thus developers are discouraged from using libevdev_new_from_fd() as
+ * important messages from the device initialization process may get lost.
+ *
+ * @note A context-specific handler cannot be used for libevdev's uinput
+ * devices. @ref uinput must use the global log handler.
+ */
+
/**
* @defgroup bits Querying device capabilities
*
void libevdev_free(struct libevdev *dev);
/**
- * @ingroup init
+ * @ingroup logging
*/
enum libevdev_log_priority {
LIBEVDEV_LOG_ERROR = 10, /**< critical errors and application bugs */
};
/**
- * @ingroup init
+ * @ingroup logging
*
* Logging function called by library-internal logging.
* This function is expected to treat its input like printf would.
LIBEVDEV_ATTRIBUTE_PRINTF(6, 0);
/**
- * @ingroup init
+ * @ingroup logging
*
* Set a printf-style logging handler for library-internal logging. The default
* logging function is to stdout.
*
+ * @note The global log handler is only called if no context-specific log
+ * handler has been set with libevdev_set_device_log_function().
+ *
* @param logfunc The logging function for this device. If NULL, the current
* logging function is unset and no logging is performed.
* @param data User-specific data passed to the log handler.
*
* @note This function may be called before libevdev_set_fd().
+ *
+ * @deprecated Use per-context logging instead, see
+ * libevdev_set_device_log_function().
*/
void libevdev_set_log_function(libevdev_log_func_t logfunc, void *data);
/**
- * @ingroup init
+ * @ingroup logging
*
* Define the minimum level to be printed to the log handler.
* Messages higher than this level are printed, others are discarded. This
*
* @param priority Minimum priority to be printed to the log.
*
+ * @deprecated Use per-context logging instead, see
+ * libevdev_set_device_log_function().
*/
void libevdev_set_log_priority(enum libevdev_log_priority priority);
/**
- * @ingroup init
+ * @ingroup logging
*
* Return the current log priority level. Messages higher than this level
* are printed, others are discarded. This is a global setting.
*
* @return the current log level
+ *
+ * @deprecated Use per-context logging instead, see
+ * libevdev_set_device_log_function().
*/
enum libevdev_log_priority libevdev_get_log_priority(void);
+/**
+ * @ingroup logging
+ *
+ * Logging function called by library-internal logging for a specific
+ * libevdev context. This function is expected to treat its input like
+ * printf would.
+ *
+ * @param dev The evdev device
+ * @param priority Log priority of this message
+ * @param data User-supplied data pointer (see libevdev_set_log_function())
+ * @param file libevdev source code file generating this message
+ * @param line libevdev source code line generating this message
+ * @param func libevdev source code function generating this message
+ * @param format printf-style format string
+ * @param args List of arguments
+ *
+ * @see libevdev_set_log_function
+ * @since 1.3
+ */
+typedef void (*libevdev_device_log_func_t)(const struct libevdev *dev,
+ enum libevdev_log_priority priority,
+ void *data,
+ const char *file, int line,
+ const char *func,
+ const char *format, va_list args)
+ LIBEVDEV_ATTRIBUTE_PRINTF(7, 0);
+
+/**
+ * @ingroup logging
+ *
+ * Set a printf-style logging handler for library-internal logging for this
+ * device context. The default logging function is NULL, i.e. the global log
+ * handler is invoked. If a context-specific log handler is set, the global
+ * log handler is not invoked for this device.
+ *
+ * @note This log function applies for this device context only, even if
+ * another context exists for the same fd.
+ *
+ * @param dev The evdev device
+ * @param logfunc The logging function for this device. If NULL, the current
+ * logging function is unset and logging falls back to the global log
+ * handler, if any.
+ * @param priority Minimum priority to be printed to the log.
+ * @param data User-specific data passed to the log handler.
+ *
+ * @note This function may be called before libevdev_set_fd().
+ * @since 1.3
+ */
+void libevdev_set_device_log_function(struct libevdev *dev,
+ libevdev_device_log_func_t logfunc,
+ enum libevdev_log_priority priority,
+ void *data);
+
/**
* @ingroup init
*/
local:
*;
};
+
+LIBEVDEV_1_3 {
+global:
+ libevdev_set_device_log_function;
+
+local:
+ *;
+} LIBEVDEV_1;
}
END_TEST
+struct libevdev *devlogdata;
+static int dev_log_fn_called = 0;
+static void devlogfunc(const struct libevdev *dev,
+ enum libevdev_log_priority priority,
+ void *data,
+ const char *file, int line, const char *func,
+ const char *f, va_list args)
+{
+ ck_assert(dev == data);
+ dev_log_fn_called++;
+}
+
+START_TEST(test_device_log_init)
+{
+ struct libevdev *dev = NULL;
+ enum libevdev_log_priority old;
+
+ old = libevdev_get_log_priority();
+ libevdev_set_log_priority(LIBEVDEV_LOG_DEBUG);
+ libevdev_set_log_function(logfunc, logdata);
+
+ /* error for NULL device */
+ libevdev_set_device_log_function(NULL, NULL,
+ LIBEVDEV_LOG_ERROR, NULL);
+ ck_assert_int_eq(log_fn_called, 1);
+
+ /* error for NULL device */
+ libevdev_set_device_log_function(NULL, devlogfunc,
+ LIBEVDEV_LOG_ERROR, NULL);
+ ck_assert_int_eq(log_fn_called, 2);
+
+ log_fn_called = 0;
+
+ dev = libevdev_new();
+ ck_assert(dev != NULL);
+
+ libevdev_set_device_log_function(dev, NULL,
+ LIBEVDEV_LOG_ERROR, NULL);
+
+ /* libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, NULL) should
+ trigger a log message. */
+
+ /* expect global handler triggered */
+ libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, NULL);
+ ck_assert_int_eq(log_fn_called, 1);
+ ck_assert_int_eq(dev_log_fn_called, 0);
+
+ /* expect device handler triggered */
+ libevdev_set_device_log_function(dev, devlogfunc,
+ LIBEVDEV_LOG_ERROR, dev);
+ libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, NULL);
+ ck_assert_int_eq(log_fn_called, 1);
+ ck_assert_int_eq(dev_log_fn_called, 1);
+
+ /* device handler set, but priority filters. don't expect any log
+ handler to be called.
+ we don't have any log msgs > ERROR at the moment, so test it by
+ setting an invalid priority. */
+ libevdev_set_device_log_function(dev, devlogfunc,
+ LIBEVDEV_LOG_ERROR - 1, dev);
+ libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, NULL);
+ ck_assert_int_eq(log_fn_called, 1);
+ ck_assert_int_eq(dev_log_fn_called, 1);
+
+ libevdev_free(dev);
+
+ log_fn_called = 0;
+ libevdev_set_log_priority(old);
+ libevdev_set_log_function(test_logfunc_abort_on_error, NULL);
+
+}
+END_TEST
+
START_TEST(test_device_init)
{
struct uinput_device* uidev;
tcase_add_test(tc, test_log_set_get_priority);
tcase_add_test(tc, test_log_default_priority);
tcase_add_test(tc, test_log_data);
+ tcase_add_test(tc, test_device_log_init);
suite_add_tcase(s, tc);
tc = tcase_create("device fd init");