#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <linux/input-event-codes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "shm.h"
#include "xdg-shell-protocol.h"
#include "wayland-tablet-protocol.h"

struct wev_filter {
	char *interface;
	char *event;
	struct wl_list link;
};

struct wev_options {
	bool print_globals;
	char *dump_map;
	struct wl_list filters;
	struct wl_list inverse_filters;
};

struct wev_state {
	struct wev_options opts;
	bool closed;

	struct wl_display *display;
	struct wl_registry *registry;
	struct wl_compositor *compositor;
	struct wl_seat *seat;
	struct wl_shm *shm;
	struct xdg_wm_base *wm_base;
	struct wl_data_device_manager *data_device_manager;

	struct wl_surface *surface;
	struct xdg_surface *xdg_surface;
	struct xdg_toplevel *xdg_toplevel;

	int32_t width, height;

	struct xkb_state *xkb_state;
	struct xkb_context *xkb_context;
	struct xkb_keymap *xkb_keymap;

	struct wl_data_offer *selection;
	struct wl_data_offer *dnd;

	struct zwp_tablet_manager_v2 *tablet_manager;
};

#define SPACER "                      "

static int proxy_log(struct wev_state *state,
		struct wl_proxy *proxy, const char *event, const char *fmt, ...) {
	const char *class = wl_proxy_get_class(proxy);

	if (!wl_list_empty(&state->opts.filters)) {
		bool found = false;
		struct wev_filter *filter;
		wl_list_for_each(filter, &state->opts.filters, link) {
			if (strcmp(filter->interface, class) == 0 &&
					(!filter->event || strcmp(filter->event, event) == 0)) {
				found = true;
			}
		}
		if (!found) {
			return 0;
		}
	}
	if (!wl_list_empty(&state->opts.inverse_filters)) {
		bool found = false;
		struct wev_filter *filter;
		wl_list_for_each(filter, &state->opts.inverse_filters, link) {
			if (strcmp(filter->interface, class) == 0 &&
					(!filter->event || strcmp(filter->event, event) == 0)) {
				found = true;
			}
		}
		if (found) {
			return 0;
		}
	}

	int n = 0;
	n += printf("[%10u:%16s] %s%s",
			wl_proxy_get_id(proxy),
			class, event, strcmp(fmt, "\n") != 0 ? ": " : "");
	va_list ap;
	va_start(ap, fmt);
	n += vprintf(fmt, ap);
	va_end(ap);
	return n;
}

static void escape_utf8(char *buf) {
	if (strcmp(buf, "\a") == 0) {
		strcpy(buf, "\\a");
	} else if (strcmp(buf, "\b") == 0) {
		strcpy(buf, "\\b");
	} else if (strcmp(buf, "\e") == 0) {
		strcpy(buf, "\\e");
	} else if (strcmp(buf, "\f") == 0) {
		strcpy(buf, "\\f");
	} else if (strcmp(buf, "\n") == 0) {
		strcpy(buf, "\\n");
	} else if (strcmp(buf, "\r") == 0) {
		strcpy(buf, "\\r");
	} else if (strcmp(buf, "\t") == 0) {
		strcpy(buf, "\\t");
	} else if (strcmp(buf, "\v") == 0) {
		strcpy(buf, "\\v");
	}
}

static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
		uint32_t serial, struct wl_surface *surface,
		wl_fixed_t surface_x, wl_fixed_t surface_y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "enter",
			"serial: %d; surface: %d, x, y: %f, %f\n",
			serial, wl_proxy_get_id((struct wl_proxy *)surface),
			wl_fixed_to_double(surface_x),
			wl_fixed_to_double(surface_y));
}

static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
		uint32_t serial, struct wl_surface *surface) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "leave", "surface: %d\n",
			wl_proxy_get_id((struct wl_proxy *)surface));
}

static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
		uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "motion",
			"time: %d; x, y: %f, %f\n", time,
			wl_fixed_to_double(surface_x),
			wl_fixed_to_double(surface_y));
}

static const char *pointer_button_str(uint32_t button) {
	switch (button) {
	case BTN_LEFT:
		return "left";
	case BTN_RIGHT:
		return "right";
	case BTN_MIDDLE:
		return "middle";
	case BTN_SIDE:
		return "side";
	case BTN_EXTRA:
		return "extra";
	case BTN_FORWARD:
		return "forward";
	case BTN_BACK:
		return "back";
	case BTN_TASK:
		return "task";
	case BTN_STYLUS:
		return "stylus";
	case BTN_STYLUS2:
		return "stylus2";
	case BTN_STYLUS3:
		return "stylus3";
	default:
		return "unknown";
	}
}

static const char *pointer_state_str(uint32_t state) {
	switch (state) {
	case WL_POINTER_BUTTON_STATE_RELEASED:
		return "released";
	case WL_POINTER_BUTTON_STATE_PRESSED:
		return "pressed";
	default:
		return "unknown state";
	}
}

static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
		uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
	struct wev_state *wev_state = data;
	proxy_log(wev_state, (struct wl_proxy *)wl_pointer, "button",
			"serial: %d; time: %d; button: %d (%s), state: %d (%s)\n",
			serial, time,
			button, pointer_button_str(button),
			state, pointer_state_str(state));
}

static const char *pointer_axis_str(uint32_t axis) {
	switch (axis) {
	case WL_POINTER_AXIS_VERTICAL_SCROLL:
		return "vertical";
	case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
		return "horizontal";
	default:
		return "unknown";
	}
}

static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
		uint32_t time, uint32_t axis, wl_fixed_t value) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis",
			"time: %d; axis: %d (%s), value: %f\n",
			time, axis, pointer_axis_str(axis), wl_fixed_to_double(value));
}

static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "frame", "\n");
}

static const char *pointer_axis_source_str(uint32_t axis_source) {
	switch (axis_source) {
	case WL_POINTER_AXIS_SOURCE_WHEEL:
		return "wheel";
	case WL_POINTER_AXIS_SOURCE_FINGER:
		return "finger";
	case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
		return "continuous";
	case WL_POINTER_AXIS_SOURCE_WHEEL_TILT:
		return "wheel tilt";
	default:
		return "unknown";
	}
}

static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
		uint32_t axis_source) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis_source",
			"%d (%s)\n", axis_source, pointer_axis_source_str(axis_source));
}

static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
		uint32_t time, uint32_t axis) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis_stop",
			"time: %d; axis: %d (%s)\n",
			time, axis, pointer_axis_str(axis));
}

static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
		uint32_t axis, int32_t discrete) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis_discrete",
			"axis: %d (%s), discrete: %d\n",
			axis, pointer_axis_str(axis), discrete);
}

static void wl_pointer_axis_value120(void *data, struct wl_pointer *wl_pointer,
		uint32_t axis, int32_t value120) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis_value120",
			"axis: %d (%s), value120: %d\n",
			axis, pointer_axis_str(axis), value120);
}

static void wl_pointer_axis_relative_direction(void *data,
		struct wl_pointer *wl_pointer, uint32_t axis,
		uint32_t direction) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_pointer, "axis_relative_direction",
			"axis: %d (%s), direction: %d\n",
			axis, pointer_axis_str(axis), direction);
}

static const struct wl_pointer_listener wl_pointer_listener = {
	.enter = wl_pointer_enter,
	.leave = wl_pointer_leave,
	.motion = wl_pointer_motion,
	.button = wl_pointer_button,
	.axis = wl_pointer_axis,
	.frame = wl_pointer_frame,
	.axis_source = wl_pointer_axis_source,
	.axis_stop = wl_pointer_axis_stop,
	.axis_discrete = wl_pointer_axis_discrete,
	.axis_value120 = wl_pointer_axis_value120,
	.axis_relative_direction = wl_pointer_axis_relative_direction,
};

static const char *keymap_format_str(uint32_t format) {
	switch (format) {
	case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP:
		return "none";
	case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1:
		return "xkb v1";
	default:
		return "unknown";
	}
}

static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
		uint32_t format, int32_t fd, uint32_t size) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_keyboard, "keymap",
			"format: %d (%s), size: %d\n",
			format, keymap_format_str(format), size);
	char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (map_shm == MAP_FAILED) {
		close(fd);
		fprintf(stderr, "Unable to mmap keymap: %s", strerror(errno));
		return;
	}
	if (state->opts.dump_map) {
		FILE *f = fopen(state->opts.dump_map, "w");
		fwrite(map_shm, 1, size, f);
		fclose(f);
	}
	if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
		munmap(map_shm, size);
		close(fd);
		return;
	}

	struct xkb_keymap *keymap = xkb_keymap_new_from_string(
			state->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1,
			XKB_KEYMAP_COMPILE_NO_FLAGS);
	munmap(map_shm, size);
	close(fd);

	struct xkb_state *xkb_state = xkb_state_new(keymap);
	xkb_keymap_unref(state->xkb_keymap);
	xkb_state_unref(state->xkb_state);
	state->xkb_keymap = keymap;
	state->xkb_state = xkb_state;
}

static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
		uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
	struct wev_state *state = data;
	int n = proxy_log(state, (struct wl_proxy *)wl_keyboard, "enter",
			"serial: %d; surface: %d\n", serial,
			wl_proxy_get_id((struct wl_proxy *)surface));
	if (n != 0) {
		uint32_t *key;
		wl_array_for_each(key, keys) {
			char buf[128];
			xkb_keysym_t sym = xkb_state_key_get_one_sym(
					state->xkb_state, *key + 8);
			xkb_keysym_get_name(sym, buf, sizeof(buf));
			printf(SPACER "sym: %-12s (%d), ", buf, sym);
			xkb_state_key_get_utf8(
					state->xkb_state, *key + 8, buf, sizeof(buf));
			escape_utf8(buf);
			printf("utf8: '%s'\n", buf);
		}
	}
}

static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
		uint32_t serial, struct wl_surface *surface) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_keyboard, "leave",
			"serial: %d; surface: %d\n", serial,
			wl_proxy_get_id((struct wl_proxy *)surface));
}

static const char *key_state_str(uint32_t state) {
	switch (state) {
	case WL_KEYBOARD_KEY_STATE_RELEASED:
		return "released";
	case WL_KEYBOARD_KEY_STATE_PRESSED:
		return "pressed";
	default:
		return "unknown";
	}
}

static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
		uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
	struct wev_state *wev_state = data;
	int n = proxy_log(wev_state, (struct wl_proxy *)wl_keyboard, "key",
			"serial: %d; time: %d; key: %d; state: %d (%s)\n",
			serial, time, key + 8, state, key_state_str(state));

	char buf[128];
	xkb_keysym_t sym = xkb_state_key_get_one_sym(wev_state->xkb_state, key + 8);
	uint32_t keycode = state == WL_KEYBOARD_KEY_STATE_PRESSED ? key + 8 : 0;

	if (n != 0) {
		xkb_keysym_get_name(sym, buf, sizeof(buf));
		printf(SPACER "sym: %-12s (%d), ", buf, sym);

		xkb_state_key_get_utf8(wev_state->xkb_state, keycode, buf, sizeof(buf));
		escape_utf8(buf);
		printf("utf8: '%s'\n", buf);
	}
}

static void print_modifiers(struct wev_state *state, uint32_t mods) {
	if (mods != 0) {
		printf(": ");
	}
	for (int i = 0; i < 32; ++i) {
		if ((mods >> i) & 1) {
			printf("%s ", xkb_keymap_mod_get_name(state->xkb_keymap, i));
		}
	}
	printf("\n");
}

static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
		uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
		uint32_t mods_locked, uint32_t group) {
	struct wev_state *state = data;
	int n = proxy_log(state, (struct wl_proxy *)wl_keyboard, "modifiers",
			"serial: %d; group: %d\n", serial, group);
	if (n != 0) {
		printf(SPACER "depressed: %08X", mods_depressed);
		print_modifiers(state, mods_depressed);
		printf(SPACER "latched: %08X", mods_latched);
		print_modifiers(state, mods_latched);
		printf(SPACER "locked: %08X", mods_locked);
		print_modifiers(state, mods_locked);
	}
	xkb_state_update_mask(state->xkb_state,
		mods_depressed, mods_latched, mods_locked, 0, 0, group);
}

static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
		int32_t rate, int32_t delay) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_keyboard, "repeat_info",
			"rate: %d keys/sec; delay: %d ms\n", rate, delay);
}

static const struct wl_keyboard_listener wl_keyboard_listener = {
	.keymap = wl_keyboard_keymap,
	.enter = wl_keyboard_enter,
	.leave = wl_keyboard_leave,
	.key = wl_keyboard_key,
	.modifiers = wl_keyboard_modifiers,
	.repeat_info = wl_keyboard_repeat_info,
};

void wl_touch_down(void *data, struct wl_touch *wl_touch,
		uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id,
		wl_fixed_t x, wl_fixed_t y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "down",
			"serial: %d; time: %d; surface: %d; id: %d; x, y: %f, %f\n",
			serial, time, wl_proxy_get_id((struct wl_proxy *)surface),
			id, wl_fixed_to_double(x), wl_fixed_to_double(y));
}

void wl_touch_up(void *data, struct wl_touch *wl_touch,
		uint32_t serial, uint32_t time, int32_t id) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "up",
			"serial: %d; time: %d; id: %d\n", serial, time, id);
}

void wl_touch_motion(void *data, struct wl_touch *wl_touch,
		uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "motion",
			"time: %d; id: %d; x, y: %f, %f\n",
			time, id, wl_fixed_to_double(x), wl_fixed_to_double(y));
}

void wl_touch_frame(void *data, struct wl_touch *wl_touch) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "frame", "\n");
}

void wl_touch_cancel(void *data, struct wl_touch *wl_touch) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "cancel", "\n");
}

void wl_touch_shape(void *data, struct wl_touch *wl_touch,
		int32_t id, wl_fixed_t major, wl_fixed_t minor) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "shape",
			"id: %d; major, minor: %f, %f\n",
			id, wl_fixed_to_double(major), wl_fixed_to_double(minor));
}

void wl_touch_orientation(void *data, struct wl_touch *wl_touch,
		int32_t id, wl_fixed_t orientation) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)wl_touch, "shape",
			"id: %d; orientation: %f\n",
			id, wl_fixed_to_double(orientation));
}

static const struct wl_touch_listener wl_touch_listener = {
	.down = wl_touch_down,
	.up = wl_touch_up,
	.motion = wl_touch_motion,
	.frame = wl_touch_frame,
	.cancel = wl_touch_cancel,
	.shape = wl_touch_shape,
	.orientation = wl_touch_orientation,
};

static void wl_seat_capabilities(void *data, struct wl_seat *wl_seat,
		uint32_t capabilities) {
	struct wev_state *state = data;
	int n = proxy_log(state, (struct wl_proxy *)wl_seat, "capabilities", "");
	if (capabilities == 0 && n != 0) {
		printf(" none");
	}
	if ((capabilities & WL_SEAT_CAPABILITY_POINTER)) {
		if (n != 0) {
			printf("pointer ");
		}
		struct wl_pointer *pointer = wl_seat_get_pointer(wl_seat);
		wl_pointer_add_listener(pointer, &wl_pointer_listener, data);
	}
	if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) {
		if (n != 0) {
			printf("keyboard ");
		}
		struct wl_keyboard *keyboard = wl_seat_get_keyboard(wl_seat);
		wl_keyboard_add_listener(keyboard, &wl_keyboard_listener, data);
	}
	if ((capabilities & WL_SEAT_CAPABILITY_TOUCH)) {
		if (n != 0) {
			printf("touch ");
		}
		struct wl_touch *touch = wl_seat_get_touch(wl_seat);
		wl_touch_add_listener(touch, &wl_touch_listener, data);
	}
	if (n != 0) {
		printf("\n");
	}
}

static void wl_seat_name(void *data, struct wl_seat *seat, const char *name) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)seat, "name", "%s\n", name);
}

static const struct wl_seat_listener wl_seat_listener = {
	.capabilities = wl_seat_capabilities,
	.name = wl_seat_name,
};

static void wl_buffer_release(void *data, struct wl_buffer *wl_buffer) {
	wl_buffer_destroy(wl_buffer);
}

static const struct wl_buffer_listener wl_buffer_listener = {
	.release = wl_buffer_release,
};

static struct wl_buffer *create_buffer(struct wev_state *state) {
	int stride = state->width * 4;
	int size = stride * state->height;

	int fd = allocate_shm_file(size);
	if (fd == -1) {
		fprintf(stderr, "Failed to create shm pool file: %s", strerror(errno));
		return NULL;
	}

	uint32_t *data = mmap(NULL, size,
			PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (data == MAP_FAILED) {
		fprintf(stderr, "shm buffer mmap failed\n");
		close(fd);
		return NULL;
	}

	struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, size);
	struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
			state->width, state->height, stride, WL_SHM_FORMAT_XRGB8888);
	wl_shm_pool_destroy(pool);
	close(fd);

	for (int y = 0; y < state->height; ++y) {
		for (int x = 0; x < state->width; ++x) {
			if ((x + y / 8 * 8) % 16 < 8) {
				data[y * state->width + x] = 0xFF666666;
			} else {
				data[y * state->width + x] = 0xFFEEEEEE;
			}
		}
	}
	munmap(data, size);

	wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL);

	return buffer;
}

static void xdg_toplevel_configure(void *data,
		struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height,
		struct wl_array *states) {
	struct wev_state *state = data;
	state->width = width;
	state->height = height;
	if (state->width == 0 || state->height == 0) {
		state->width = 640;
		state->height = 480;
	}
	int n = proxy_log(state, (struct wl_proxy *)xdg_toplevel, "configure",
			"width: %d; height: %d", width, height);
	if (n != 0) {
		if (states->size > 0) {
			printf("\n" SPACER);
		}
		uint32_t *s;
		wl_array_for_each(s, states) {
			switch (*s) {
			case XDG_TOPLEVEL_STATE_MAXIMIZED:
				printf("maximized ");
				break;
			case XDG_TOPLEVEL_STATE_FULLSCREEN:
				printf("fullscreen ");
				break;
			case XDG_TOPLEVEL_STATE_RESIZING:
				printf("resizing ");
				break;
			case XDG_TOPLEVEL_STATE_ACTIVATED:
				printf("activated ");
				break;
			case XDG_TOPLEVEL_STATE_TILED_LEFT:
				printf("tiled-left ");
				break;
			case XDG_TOPLEVEL_STATE_TILED_RIGHT:
				printf("tiled-right ");
				break;
			case XDG_TOPLEVEL_STATE_TILED_TOP:
				printf("tiled-top ");
				break;
			case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
				printf("tiled-bottom ");
				break;
			}
		}
		printf("\n");
	}
}

static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) {
	struct wev_state *state = data;
	state->closed = true;
	proxy_log(state, (struct wl_proxy *)xdg_toplevel, "close", "\n");
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
	.configure = xdg_toplevel_configure,
	.close = xdg_toplevel_close,
};

static void xdg_surface_configure(
		void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)xdg_surface, "configure",
			"serial: %d\n", serial);
	xdg_surface_ack_configure(xdg_surface, serial);
	struct wl_buffer *buffer = create_buffer(state);
	wl_surface_attach(state->surface, buffer, 0, 0);
	wl_surface_damage_buffer(state->surface, 0, 0, INT32_MAX, INT32_MAX);
	wl_surface_commit(state->surface);
}

static const struct xdg_surface_listener xdg_surface_listener = {
	.configure = xdg_surface_configure,
};

static void wm_base_ping(void *data,
		struct xdg_wm_base *wm_base, uint32_t serial) {
	xdg_wm_base_pong(wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
	.ping = wm_base_ping,
};

static void wl_data_offer_offer(void *data, struct wl_data_offer *offer,
		const char * mime_type) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)offer, "offer",
			"mime_type: %s\n", mime_type);
}

static const char *dnd_actions_str(uint32_t state) {
	switch (state) {
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE:
		return "none";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY:
		return "copy";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE:
		return "move";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
			WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE:
		return "copy, move";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK:
		return "ask";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
			WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK:
		return "copy, ask";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE |
			WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK:
		return "move, ask";
	case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
			WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE |
			WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK:
		return "copy, move, ask";
	default:
		return "unknown";
	}
}

static void wl_data_offer_source_actions(void *data,
		struct wl_data_offer *offer, uint32_t actions) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)offer, "source_actions",
			"actions: %u (%s)\n", actions, dnd_actions_str(actions));
}

static void wl_data_offer_action(void *data, struct wl_data_offer *offer,
		uint32_t dnd_action) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)offer, "action",
			"dnd_action: %u (%s)\n", dnd_action, dnd_actions_str(dnd_action));
}

static const struct wl_data_offer_listener wl_data_offer_listener = {
	.offer = wl_data_offer_offer,
	.source_actions = wl_data_offer_source_actions,
	.action = wl_data_offer_action,
};

static void wl_data_device_data_offer(void *data,
		struct wl_data_device *device, struct wl_data_offer *id) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)device, "data_offer",
			"id: %u\n", wl_proxy_get_id((struct wl_proxy *)id));

	wl_data_offer_add_listener(id, &wl_data_offer_listener, data);
}

static void wl_data_device_enter(void *data,
		struct wl_data_device *device, uint32_t serial,
		struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y,
		struct wl_data_offer *id) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)device, "enter",
			"serial: %d; surface: %d; x, y: %f, %f; id: %u\n", serial,
			wl_proxy_get_id((struct wl_proxy *)surface),
			wl_fixed_to_double(x), wl_fixed_to_double(y),
			wl_proxy_get_id((struct wl_proxy *)id));

	state->dnd = id;
	wl_data_offer_set_actions(id,
			WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
				WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE |
				WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK,
			WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);

	// Static accept just so we have something.
	wl_data_offer_accept(id, serial, "text/plain");
}

static void wl_data_device_leave(void *data,
		struct wl_data_device *device) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)device, "leave", "\n");

	// Might have already been destroyed during a drop event.
	if (state->dnd != NULL) {
		wl_data_offer_destroy(state->dnd);
		state->dnd = NULL;
	}
}

static void wl_data_device_motion(void *data,
		struct wl_data_device *device, uint32_t serial, wl_fixed_t x,
		wl_fixed_t y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)device, "motion",
			"serial: %d; x, y: %f, %f\n", serial, wl_fixed_to_double(x),
			wl_fixed_to_double(y));
}

static void wl_data_device_drop(void *data,
		struct wl_data_device *device) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)device, "drop", "\n");

	// We don't actually want the data, so cancel the drop.
	wl_data_offer_destroy(state->dnd);
	state->dnd = NULL;
}

static void wl_data_device_selection(void *data,
		struct wl_data_device *device, struct wl_data_offer *id) {
	struct wev_state *state = data;
	if (id == NULL) {
		proxy_log(state, (struct wl_proxy *)device, "selection",
				"(cleared)\n");
	}
	else {
		proxy_log(state, (struct wl_proxy *)device, "selection", "id: %u\n",
				wl_proxy_get_id((struct wl_proxy *)id));
	}

	if (state->selection != NULL) {
		wl_data_offer_destroy(state->selection);
	}
	state->selection = id;  // May be NULL.
}

static const struct wl_data_device_listener wl_data_device_listener = {
	.data_offer = wl_data_device_data_offer,
	.enter = wl_data_device_enter,
	.leave = wl_data_device_leave,
	.motion = wl_data_device_motion,
	.drop = wl_data_device_drop,
	.selection = wl_data_device_selection,
};

static void tablet_name(void *data, struct zwp_tablet_v2 *tablet,
		const char *name) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "name", "%s\n", name);
}

static void tablet_id(void *data, struct zwp_tablet_v2 *tablet,
		uint32_t vid, uint32_t pid) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "id",
			"vid: 0x%04x pid: 0x%04x\n", vid, pid);
}

static void tablet_path(void *data, struct zwp_tablet_v2 *tablet,
		const char *path) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "path", "%s\n", path);
}

static void tablet_done(void *data, struct zwp_tablet_v2 *tablet) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "done", "\n");
}

static void tablet_removed(void *data, struct zwp_tablet_v2 *tablet) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "removed", "\n");
}

static void tablet_bustype(void *data, struct zwp_tablet_v2 *tablet,
		uint32_t bustype) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet, "bustype", "bustype: %d\n", bustype);
}

static const struct zwp_tablet_v2_listener tablet_listener = {
	.name = tablet_name,
	.id = tablet_id,
	.path = tablet_path,
	.done = tablet_done,
	.removed = tablet_removed,
	.bustype = tablet_bustype,
};

static void tablet_added(void *data, struct zwp_tablet_seat_v2 *seat,
		struct zwp_tablet_v2 *id) {
	struct wev_state *state = data;
	zwp_tablet_v2_add_listener(id, &tablet_listener, state);
	proxy_log(state, (struct wl_proxy *)seat, "tablet_added", "%u\n",
			wl_proxy_get_id((struct wl_proxy *)id));
}

static const char *tablet_tool_type_str(uint32_t type) {
	switch (type) {
	case ZWP_TABLET_TOOL_V2_TYPE_PEN:
		return "pen";
	case ZWP_TABLET_TOOL_V2_TYPE_ERASER:
		return "eraser";
	case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
		return "brush";
	case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
		return "pencil";
	case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
		return "airbrush";
	case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
		return "finger";
	case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
		return "mouse";
	case ZWP_TABLET_TOOL_V2_TYPE_LENS:
		return "lens";
	default:
		return "unknown";
	}
}

static void tablet_tool_type(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool, uint32_t type) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "type",
			"type: %d (%s)\n", type, tablet_tool_type_str(type));
}

static void tablet_tool_hardware_serial(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t hw_serial_hi, uint32_t hw_serial_lo) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool,
			"hardware_serial", "hi, lo: 0x%08x 0x%08x\n",
			hw_serial_hi, hw_serial_lo);
}

static void tablet_tool_hardware_id_wacom(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t hardware_id_hi, uint32_t hardware_id_lo) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool,
			"hardware_id_wacom", "hi, lo: 0x%08x 0x%08x\n",
			hardware_id_hi, hardware_id_lo);
}

static const char *tablet_tool_capability_str(uint32_t capability) {
	switch (capability) {
	case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT :
		return "tilt";
	case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE :
		return "pressure";
	case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE :
		return "distance";
	case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION :
		return "rotation";
	case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER :
		return "slider";
	case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL :
		return "wheel";
	default:
		return "unknown";
	}
}

static void tablet_tool_capability(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool, uint32_t capability) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "capability",
			"capability: %d (%s)\n", capability,
			tablet_tool_capability_str(capability));
}

static void tablet_tool_done(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "done", "\n");
}

static void tablet_tool_removed(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "removed", "\n");
}

static void tablet_tool_proximity_in(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t serial, struct zwp_tablet_v2 *tablet,
		struct wl_surface *surface) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "proximity_in",
			"serial: %d; tablet: %u; surface: %d\n",
			serial, wl_proxy_get_id((struct wl_proxy *)tablet),
			wl_proxy_get_id((struct wl_proxy *)surface));
}

static void tablet_tool_proximity_out(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool,
			"proximity_out", "\n");
}

static void tablet_tool_down(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t serial) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "down",
			"serial: %d\n", serial);
}

static void tablet_tool_up(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "up", "\n");
}

static void tablet_tool_motion(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		wl_fixed_t x, wl_fixed_t y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "motion",
			"x, y: %f, %f\n",
			wl_fixed_to_double(x), wl_fixed_to_double(y));
}

static void tablet_tool_pressure(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool, uint32_t pressure) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "pressure",
			"%d (%.3f)\n", pressure,
			(double)pressure/0xffff);
}

static void tablet_tool_distance(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t distance) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "distance",
			"%d (%.3f)\n", distance,
			(double)distance/0xffff);
}

static void tablet_tool_tilt(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		wl_fixed_t x, wl_fixed_t y) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "tilt",
			"x, y: %f, %f\n",
			wl_fixed_to_double(x), wl_fixed_to_double(y));
}

static void tablet_tool_rotation(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		wl_fixed_t degrees) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "rotation",
			"degrees: %f\n", wl_fixed_to_double(degrees));
}

static void tablet_tool_slider(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		int32_t position) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "slider",
			"position: %d (%.3f)\n", position,
			(double)position/0xffff);
}

static void tablet_tool_wheel(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		wl_fixed_t degrees, int32_t clicks) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "wheel",
			"wheel: %f, clicks: %d\n", degrees, clicks);
}

static void tablet_tool_button(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool,
		uint32_t serial, uint32_t button, uint32_t state) {
	struct wev_state *wev_state = data;
	proxy_log(wev_state, (struct wl_proxy *)tablet_tool, "button",
			"serial: %d;  button: %d (%s), state: %d (%s)\n",
			serial, button, pointer_button_str(button),
			state, pointer_state_str(state));
}

static void tablet_tool_frame(void *data,
		struct zwp_tablet_tool_v2 *tablet_tool, uint32_t time) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)tablet_tool, "frame",
			"time: %d\n", time);
}

static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
	.type = tablet_tool_type,
	.hardware_serial = tablet_tool_hardware_serial,
	.hardware_id_wacom = tablet_tool_hardware_id_wacom,
	.capability = tablet_tool_capability,
	.done = tablet_tool_done,
	.removed = tablet_tool_removed,
	.proximity_in = tablet_tool_proximity_in,
	.proximity_out = tablet_tool_proximity_out,
	.down = tablet_tool_down,
	.up = tablet_tool_up,
	.motion = tablet_tool_motion,
	.pressure = tablet_tool_pressure,
	.distance = tablet_tool_distance,
	.tilt = tablet_tool_tilt,
	.rotation = tablet_tool_rotation,
	.slider = tablet_tool_slider,
	.wheel = tablet_tool_wheel,
	.button = tablet_tool_button,
	.frame = tablet_tool_frame,
};

static const char *tablet_ring_axis_source_str(uint32_t axis_source) {
	switch (axis_source) {
	case ZWP_TABLET_PAD_RING_V2_SOURCE_FINGER:
		return "finger";
	default:
		return "unknown";
	}
}

static void tablet_ring_source(void *data,
		struct zwp_tablet_pad_ring_v2 *ring, uint32_t source) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)ring, "source",
			"%d (%s)\n", source,
			tablet_ring_axis_source_str(source));
}

static void tablet_ring_angle(void *data,
		struct zwp_tablet_pad_ring_v2 *ring, wl_fixed_t degrees) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)ring, "angle",
			"%f\n", wl_fixed_to_double(degrees));
}

static void tablet_ring_stop(void *data,
		struct zwp_tablet_pad_ring_v2 *ring) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)ring, "stop", "\n");
}

static void tablet_ring_frame(void *data,
		struct zwp_tablet_pad_ring_v2 *ring, uint32_t time) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)ring, "frame",
			"time: %d\n", time);
}

static const struct zwp_tablet_pad_ring_v2_listener tablet_ring_listener = {
	.source = tablet_ring_source,
	.angle = tablet_ring_angle,
	.stop = tablet_ring_stop,
	.frame = tablet_ring_frame,
};

static const char *tablet_strip_axis_source_str(uint32_t axis_source) {
	switch (axis_source) {
	case ZWP_TABLET_PAD_STRIP_V2_SOURCE_FINGER:
		return "finger";
	default:
		return "unknown";
	}
}

static void tablet_strip_source(void *data,
		struct zwp_tablet_pad_strip_v2 *strip, uint32_t source) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)strip, "source",
			"%d (%s)\n", source,
			tablet_strip_axis_source_str(source));
}

static void tablet_strip_position(void *data,
		struct zwp_tablet_pad_strip_v2 *strip, uint32_t position) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)strip, "position",
			"%f\n", wl_fixed_to_double(position));
}

static void tablet_strip_stop(void *data, struct zwp_tablet_pad_strip_v2 *strip) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)strip, "stop", "\n");
}

static void tablet_strip_frame(void *data,
		struct zwp_tablet_pad_strip_v2 *strip, uint32_t time) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)strip, "frame",
			"time: %d\n", time);
}

static const struct zwp_tablet_pad_strip_v2_listener tablet_strip_listener = {
	.source = tablet_strip_source,
	.position = tablet_strip_position,
	.stop = tablet_strip_stop,
	.frame = tablet_strip_frame,
};

static void tablet_dial_delta(void *data,
		struct zwp_tablet_pad_dial_v2 *dial, int32_t value120) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)dial, "delta",
			"%d\n", value120);
}

static void tablet_dial_frame(void *data,
		struct zwp_tablet_pad_dial_v2 *dial, uint32_t time) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)dial, "frame",
			"time: %d\n", time);
}

static const struct zwp_tablet_pad_dial_v2_listener tablet_dial_listener = {
	.delta = tablet_dial_delta,
	.frame = tablet_dial_frame,
};

static void tablet_pad_group_buttons(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		struct wl_array *buttons) {
	struct wev_state *state = data;
	uint32_t *b;

	int n = proxy_log(state, (struct wl_proxy *)pad_group, "buttons",
			"buttons: ");
	if (n != 0) {
		wl_array_for_each(b, buttons) {
			printf("%d ", *b);
		}
		printf("\n");
	}
}

static void tablet_pad_group_ring(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		struct zwp_tablet_pad_ring_v2 *ring) {
	struct wev_state *state = data;
	zwp_tablet_pad_ring_v2_add_listener(ring, &tablet_ring_listener, state);
}

static void tablet_pad_group_strip(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		struct zwp_tablet_pad_strip_v2 *strip) {
	struct wev_state *state = data;
	zwp_tablet_pad_strip_v2_add_listener(strip, &tablet_strip_listener, state);
}

static void tablet_pad_group_modes(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		uint32_t modes) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad_group, "modes",
			"%d\n", modes);
}

static void tablet_pad_group_done(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad_group, "done", "\n");
}

static void tablet_pad_group_mode_switch(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		uint32_t time, uint32_t serial, uint32_t mode) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad_group, "mode_switch",
			"time: %d; serial: %d; mode: %d\n",
			time, serial, mode);
}

static void tablet_pad_group_dial(void *data,
		struct zwp_tablet_pad_group_v2 *pad_group,
		struct zwp_tablet_pad_dial_v2 *dial) {
	struct wev_state *state = data;
	zwp_tablet_pad_dial_v2_add_listener(dial, &tablet_dial_listener, state);
}

static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = {
	.buttons = tablet_pad_group_buttons,
	.ring = tablet_pad_group_ring,
	.strip = tablet_pad_group_strip,
	.modes = tablet_pad_group_modes,
	.done = tablet_pad_group_done,
	.mode_switch = tablet_pad_group_mode_switch,
	.dial = tablet_pad_group_dial,
};

static void tablet_tool_added(void *data, struct zwp_tablet_seat_v2 *seat,
		struct zwp_tablet_tool_v2 *id) {
	struct wev_state *state = data;
	zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listener, state);
	proxy_log(state, (struct wl_proxy *)seat, "tool_added", "%u\n",
			wl_proxy_get_id((struct wl_proxy *)id));
}

static void tablet_pad_group(void *data, struct zwp_tablet_pad_v2 *pad,
		struct zwp_tablet_pad_group_v2 *pad_group) {
	struct wev_state *state = data;
	zwp_tablet_pad_group_v2_add_listener (pad_group,
			&tablet_pad_group_listener, state);
	proxy_log(state, (struct wl_proxy *)pad, "group", "%u\n",
			wl_proxy_get_id((struct wl_proxy *)pad_group));
}

static void tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad,
		const char *path) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "path", "%s\n", path);
}

static void tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad,
		uint32_t buttons) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "buttons",
			"%d\n", buttons);
}

static void tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "done", "\n");
}

static void tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad,
		uint32_t time, uint32_t button, uint32_t state) {
	struct wev_state *wev_state = data;
	proxy_log(wev_state, (struct wl_proxy *)pad, "button",
			"time: %d; button: %d, state: %d (%s)\n",
			time, button, state, pointer_state_str(state));
}

static void tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad,
		uint32_t serial, struct zwp_tablet_v2 *tablet,
		struct wl_surface *surface) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "enter",
			"serial: %d; tablet: %u; surface: %d\n", serial,
			wl_proxy_get_id((struct wl_proxy *)tablet),
			wl_proxy_get_id((struct wl_proxy *)surface));
}

static void tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad,
		uint32_t serial, struct wl_surface *surface) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "leave",
			"serial: %d; surface: %d\n", serial,
			wl_proxy_get_id((struct wl_proxy *)surface));
}

static void tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) {
	struct wev_state *state = data;
	proxy_log(state, (struct wl_proxy *)pad, "removed", "\n");
}

static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = {
	.group = tablet_pad_group,
	.path = tablet_pad_path,
	.buttons = tablet_pad_buttons,
	.done = tablet_pad_done,
	.button = tablet_pad_button,
	.enter = tablet_pad_enter,
	.leave = tablet_pad_leave,
	.removed = tablet_pad_removed,
};

static void tablet_pad_added(void *data,
		struct zwp_tablet_seat_v2 *seat,
		struct zwp_tablet_pad_v2 *id) {
	struct wev_state *state = data;
	zwp_tablet_pad_v2_add_listener(id, &tablet_pad_listener, state);
	proxy_log(state, (struct wl_proxy *)seat, "pad_added", "%u\n",
			wl_proxy_get_id((struct wl_proxy *)id));
}

static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
	.tablet_added = tablet_added,
	.tool_added = tablet_tool_added,
	.pad_added = tablet_pad_added,
};

static void registry_global(void *data, struct wl_registry *wl_registry,
		uint32_t name, const char *interface, uint32_t version) {
	struct wev_state *state = data;
	struct {
		const struct wl_interface *interface;
		int version;
		void **ptr;
	} handles[] = {
		{ &wl_compositor_interface, 4, (void **)&state->compositor },
		{ &wl_seat_interface, 9, (void **)&state->seat },
		{ &wl_shm_interface, 1, (void **)&state->shm },
		{ &xdg_wm_base_interface, 2, (void **)&state->wm_base },
		{ &wl_data_device_manager_interface, 3,
			(void **)&state->data_device_manager },
		{ &zwp_tablet_manager_v2_interface, 2, (void **)&state->tablet_manager },
	};

	for (size_t i = 0; i < sizeof(handles) / sizeof(handles[0]); ++i) {
		if (strcmp(interface, handles[i].interface->name) == 0) {
			if (handles[i].version < version) {
				version = handles[i].version;
			}
			*handles[i].ptr = wl_registry_bind(wl_registry,
					name, handles[i].interface, version);
		}
	}

	if (state->opts.print_globals) {
		proxy_log(state, (struct wl_proxy *)wl_registry, "global",
				"interface: '%s', version: %d, name: %d\n",
				interface, version, name);
	}
}

static void registry_global_remove(
		void *data, struct wl_registry *wl_registry, uint32_t name) {
	/* Who cares */
}

static const struct wl_registry_listener wl_registry_listener = {
	.global = registry_global,
	.global_remove = registry_global_remove,
};

void show_usage(void) {
	printf("Usage: wev [-g] "
			"[-f <interface[:event]>] [-F <interface[:event]>] [-M <path>]\n");
}

void add_filter(struct wl_list *list, char *filter) {
	char *iface = strtok(filter, ":");
	char *event = strtok(NULL, ":");
	struct wev_filter *f = calloc(1, sizeof(struct wev_filter));
	f->interface = strdup(iface);
	f->event = event ? strdup(event) : 0;
	wl_list_insert(list, &f->link);
}

int main(int argc, char *argv[]) {
	struct wev_state state = { 0 };
	wl_list_init(&state.opts.filters);
	wl_list_init(&state.opts.inverse_filters);

	int opt;
	while ((opt = getopt(argc, argv, "f:F:ghM:")) != -1) {
		switch (opt) {
		case 'f':
			add_filter(&state.opts.filters, optarg);
			break;
		case 'F':
			add_filter(&state.opts.inverse_filters, optarg);
			break;
		case 'g':
			state.opts.print_globals = true;
			break;
		case 'h':
			show_usage();
			return 0;
		case 'M':
			state.opts.dump_map = optarg;
			break;
		default:
			show_usage();
			return 1;
		}
	}
	if (optind < argc) {
		show_usage();
		return 1;
	}

	state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);

	state.display = wl_display_connect(NULL);
	if (!state.display) {
		fprintf(stderr, "Failed to connect to Wayland display\n");
		return 1;
	}
	state.registry = wl_display_get_registry(state.display);
	if (!state.registry) {
		fprintf(stderr, "Failed to obtain Wayland registry\n");
		return 1;
	}
	wl_registry_add_listener(state.registry, &wl_registry_listener, &state);
	wl_display_roundtrip(state.display);

	struct {
		char *name;
		void *ptr;
	} required[] = {
		{ "wl_compositor", state.compositor, },
		{ "wl_seat", state.seat, },
		{ "wl_shm", state.shm, },
		{ "xdg_wm_base", state.wm_base, },
		{ "wl_data_device_manager", state.data_device_manager, },
	};
	for (size_t i = 0; i < sizeof(required) / sizeof(required[0]); ++i) {
		if (required->ptr == NULL) {
			fprintf(stderr, "%s is required but is not present.\n",
					required[i].name);
			return 1;
		}
	}

	xdg_wm_base_add_listener(state.wm_base, &xdg_wm_base_listener, NULL);

	state.surface = wl_compositor_create_surface(state.compositor);
	state.xdg_surface = xdg_wm_base_get_xdg_surface(
			state.wm_base, state.surface);
	xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);

	state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
	xdg_toplevel_set_title(state.xdg_toplevel, "wev");
	xdg_toplevel_set_app_id(state.xdg_toplevel, "wev");
	xdg_toplevel_add_listener(state.xdg_toplevel,
			&xdg_toplevel_listener, &state);

	wl_seat_add_listener(state.seat, &wl_seat_listener, &state);

	struct wl_data_device *data_device =
		wl_data_device_manager_get_data_device(state.data_device_manager,
				state.seat);
	wl_data_device_add_listener(data_device, &wl_data_device_listener, &state);

	if (state.tablet_manager) {
		struct zwp_tablet_seat_v2 *tablet_seat =
			zwp_tablet_manager_v2_get_tablet_seat(state.tablet_manager, state.seat);
		zwp_tablet_seat_v2_add_listener(tablet_seat, &tablet_seat_listener, &state);
	}

	wl_surface_commit(state.surface);
	wl_display_roundtrip(state.display);

	while (wl_display_dispatch(state.display) && !state.closed) {
		/* This space deliberately left blank */
	}

	return 0;
}
