// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT

#define _POSIX_C_SOURCE 200812L

#include "config.h"

#include <linux/input-event-codes.h>
#include <stdint.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wayland-server-protocol.h>
#include <wlr/backend.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#if NEDM_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif

#include "input_manager.h"
#include "keybinding.h"
#include "message.h"
#include "output.h"
#include "seat.h"
#include "server.h"
#include "view.h"
#include "workspace.h"
#if NEDM_HAS_XWAYLAND
#include "xwayland.h"
#endif

static void
drag_icon_update_position(struct nedm_drag_icon *drag_icon);

static void
update_capabilities(const struct nedm_seat *seat) {
	uint32_t caps = 0;

	if(seat->num_keyboards > 0) {
		caps |= WL_SEAT_CAPABILITY_KEYBOARD;
	}
	if(seat->num_pointers > 0) {
		caps |= WL_SEAT_CAPABILITY_POINTER;
	}
	if(seat->num_touch > 0) {
		caps |= WL_SEAT_CAPABILITY_TOUCH;
	}
	wlr_seat_set_capabilities(seat->seat, caps);

	/* Hide cursor if the seat doesn't have pointer capability. */
	if(((caps & WL_SEAT_CAPABILITY_POINTER) == 0) ||
	   seat->enable_cursor == false) {
		wlr_cursor_unset_image(seat->cursor);
	} else {
		wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager,
		                       DEFAULT_XCURSOR);
	}
}

static void
remove_touch(struct nedm_seat *seat, struct nedm_touch *touch) {
	if(!touch) {
		return;
	}
	wlr_cursor_detach_input_device(seat->cursor, touch->device->wlr_device);
	--seat->num_touch;
	free(touch);
}

static void
new_touch(struct nedm_seat *seat, struct nedm_input_device *input_device) {
	struct wlr_input_device *device = input_device->wlr_device;
	struct wlr_touch *wlr_touch = wlr_touch_from_input_device(device);
	struct nedm_touch *touch = calloc(1, sizeof(struct nedm_touch));
	if(!touch) {
		wlr_log(WLR_ERROR, "Cannot allocate touch");
		return;
	}

	touch->seat = seat;
	touch->device = input_device;
	wlr_cursor_attach_input_device(seat->cursor, device);
	input_device->touch = touch;
	++seat->num_touch;

	if(wlr_touch->output_name != NULL) {
		struct nedm_output *output;
		wl_list_for_each(output, &seat->server->outputs, link) {
			if(strcmp(wlr_touch->output_name, output->name) == 0) {
				wlr_cursor_map_input_to_output(seat->cursor, device,
				                               output->wlr_output);
				break;
			}
		}
	}
}

static void
remove_pointer(struct nedm_seat *seat, struct nedm_pointer *pointer) {
	if(!pointer) {
		return;
	}
	wlr_cursor_detach_input_device(seat->cursor, pointer->device->wlr_device);
	--seat->num_pointers;
	free(pointer);
}

static void
new_pointer(struct nedm_seat *seat, struct nedm_input_device *input_device) {
	struct wlr_input_device *device = input_device->wlr_device;
	struct wlr_pointer *wlr_pointer = wlr_pointer_from_input_device(device);
	struct nedm_pointer *pointer = calloc(1, sizeof(struct nedm_pointer));
	if(!pointer) {
		wlr_log(WLR_ERROR, "Cannot allocate pointer");
		return;
	}

	pointer->seat = seat;
	pointer->device = input_device;
	wlr_cursor_attach_input_device(seat->cursor, device);
	input_device->pointer = pointer;
	++seat->num_pointers;

	if(wlr_pointer->output_name != NULL) {
		struct nedm_output *output;
		wl_list_for_each(output, &seat->server->outputs, link) {
			if(strcmp(wlr_pointer->output_name, output->name) == 0) {
				wlr_cursor_map_input_to_output(seat->cursor, device,
				                               output->wlr_output);
				break;
			}
		}
	}
}

static int
handle_keyboard_repeat(void *data) {
	struct nedm_keyboard_group *nedm_group = data;
	struct wlr_keyboard *wlr_device = &nedm_group->wlr_group->keyboard;
	if(nedm_group->repeat_keybinding != NULL) {
		if(wlr_device->repeat_info.rate > 0) {
			if(wl_event_source_timer_update(
			       nedm_group->key_repeat_timer,
			       1000 / wlr_device->repeat_info.rate) < 0) {
				wlr_log(WLR_DEBUG, "failed to update key repeat timer");
			}
		}
		run_action((*nedm_group->repeat_keybinding)->action,
		           nedm_group->seat->server,
		           (*nedm_group->repeat_keybinding)->data);
	}
	return 0;
}

static void
handle_modifier_event(struct wlr_input_device *device, struct nedm_seat *seat) {
	struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device);
	wlr_seat_set_keyboard(seat->seat, keyboard);
	wlr_seat_keyboard_notify_modifiers(seat->seat, &keyboard->modifiers);

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

void
keyboard_disarm_key_repeat(struct nedm_keyboard_group *group) {
	if(!group) {
		return;
	}
	group->repeat_keybinding = NULL;
	if(wl_event_source_timer_update(group->key_repeat_timer, 0) < 0) {
		wlr_log(WLR_DEBUG, "failed to disarm key repeat timer");
	}
}

static bool
handle_command_key_bindings(struct nedm_server *server, xkb_keysym_t sym,
                            uint32_t modifiers, uint32_t mode,
                            struct nedm_keyboard_group *group) {
	if(group->enable_keybindings == false) {
		return false;
	}
	struct keybinding **keybinding = find_keybinding(
	    server->keybindings,
	    &(struct keybinding){.key = sym, .mode = mode, .modifiers = modifiers});
	if(server->seat->mode != server->seat->default_mode) {
		server->seat->mode =
		    server->seat
		        ->default_mode; // Return to mode we are currently in by default
		double sx, sy;
		struct wlr_seat *wlr_seat = server->seat->seat;
		struct wlr_surface *surface = NULL;

		struct wlr_scene_node *node = wlr_scene_node_at(
		    &server->scene->tree.node, server->seat->cursor->x,
		    server->seat->cursor->y, &sx, &sy);
		if(server->seat->enable_cursor) {
			wlr_cursor_set_xcursor(server->seat->cursor,
			                       server->seat->xcursor_manager, "left_ptr");
			if(node && node->type == WLR_SCENE_NODE_BUFFER) {
				struct wlr_scene_surface *scene_surface =
				    wlr_scene_surface_try_from_buffer(
				        wlr_scene_buffer_from_node(node));
				if(scene_surface != NULL) {
					surface = scene_surface->surface;
					if(surface != NULL) {
						wlr_seat_pointer_notify_enter(wlr_seat, surface, sx,
						                              sy);
					}
				}
			}
		}
	}

	if(keybinding) {
		wlr_log(
		    WLR_DEBUG,
		    "Recognized keybinding pressed (key: %d, mode: %d, modifiers: %d)",
		    sym, mode, modifiers);

		if(group->wlr_group->keyboard.repeat_info.delay > 0) {
			group->repeat_keybinding = keybinding;
			if(wl_event_source_timer_update(
			       group->key_repeat_timer,
			       group->wlr_group->keyboard.repeat_info.delay) < 0) {
				wlr_log(WLR_DEBUG, "failed to set key repeat timer");
			}
		}
		message_clear(group->seat->server->curr_output);
		run_action((*keybinding)->action, server, (*keybinding)->data);
		wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat);
		return true;
	} else if(mode != 0) {
		run_action(KEYBINDING_NOOP, server, (union keybinding_params){NULL});
		message_printf(server->curr_output, "unbound key pressed");
		wlr_log(WLR_DEBUG,
		        "Unknown keybinding pressed (key: %d, mode: %d, modifiers: %d)",
		        sym, mode, modifiers);
		return true;
	} else {
		return false;
	}
}

static bool
key_is_modifier(const xkb_keysym_t key) {
	switch(key) {
	case XKB_KEY_Shift_L:
	case XKB_KEY_Shift_R:
	case XKB_KEY_Control_L:
	case XKB_KEY_Control_R:
	case XKB_KEY_Alt_L:
	case XKB_KEY_Alt_R:
	case XKB_KEY_Super_L:
	case XKB_KEY_Super_R:
	case XKB_KEY_Hyper_L:
	case XKB_KEY_Hyper_R:
		return true;
	default:
		return false;
	}
}

static void
handle_key_event(struct nedm_keyboard_group *group, struct nedm_seat *seat,
                 void *data) {
	struct wlr_keyboard_key_event *event = data;
	struct wlr_keyboard *keyboard = &group->wlr_group->keyboard;

	/* Translate from libinput keycode to an xkbcommon keycode. */
	xkb_keycode_t keycode = event->keycode + 8;

	const xkb_keysym_t *syms;
	int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms);

	bool handled = false;

	for(int i = 0; i < nsyms; ++i) {
		if(event->state == WL_KEYBOARD_KEY_STATE_PRESSED &&
		   !key_is_modifier(syms[i])) {
			uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
			/* Get the consumed_modifiers and remove them from the modifier list
			 */
			xkb_mod_mask_t consumed_modifiers =
			    xkb_state_key_get_consumed_mods2(keyboard->xkb_state, keycode,
			                                     XKB_CONSUMED_MODE_GTK);
			if(handle_command_key_bindings(seat->server, syms[i],
			                               modifiers & ~consumed_modifiers,
			                               seat->mode, group)) {
				handled = true;
			}
		} else if(group->repeat_keybinding != NULL && handled == false) {
			keyboard_disarm_key_repeat(group);
		}
	}

	if(!handled) {
		/* Otherwise, we pass it along to the client. */
		wlr_seat_set_keyboard(seat->seat, keyboard);
		wlr_seat_keyboard_notify_key(seat->seat, event->time_msec,
		                             event->keycode, event->state);
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_keyboard_group_key(struct wl_listener *listener, void *data) {
	struct nedm_keyboard_group *nedm_group =
	    wl_container_of(listener, nedm_group, key);
	handle_key_event(nedm_group, nedm_group->seat, data);
}

static void
handle_keyboard_group_modifiers(struct wl_listener *listener,
                                __attribute__((unused)) void *_data) {
	struct nedm_keyboard_group *group =
	    wl_container_of(listener, group, modifiers);
	handle_modifier_event(&group->wlr_group->keyboard.base, group->seat);
}

static bool
repeat_info_match(struct wlr_keyboard *a, struct wlr_keyboard *b) {
	return a->repeat_info.rate == b->repeat_info.rate &&
	       a->repeat_info.delay == b->repeat_info.delay;
}

static void
nedm_keyboard_group_add(struct nedm_input_device *input_device,
                      struct nedm_seat *seat) {
	struct wlr_input_device *device = input_device->wlr_device;
	struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device);
	// Virtual devices should not be grouped
	if(!input_device->is_virtual) {
		struct nedm_keyboard_group *group;
		wl_list_for_each(group, &seat->keyboard_groups, link) {
			struct wlr_keyboard_group *wlr_group = group->wlr_group;
			if(wlr_keyboard_keymaps_match(wlr_keyboard->keymap,
			                              wlr_group->keyboard.keymap) &&
			   repeat_info_match(wlr_keyboard, &wlr_group->keyboard) &&
			   wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) {
				wlr_log(WLR_DEBUG, "Adding keyboard to existing group.");
				return;
			}
		}
	}
	/* This is reached if and only if the keyboard could not be inserted into
	 * any group */
	struct nedm_keyboard_group *nedm_group =
	    calloc(1, sizeof(struct nedm_keyboard_group));
	if(nedm_group == NULL) {
		wlr_log(WLR_ERROR, "Failed to allocate keyboard group.");
		return;
	}
	nedm_group->seat = seat;
	nedm_group->wlr_group = wlr_keyboard_group_create();
	if(nedm_group->wlr_group == NULL) {
		wlr_log(WLR_ERROR, "Failed to create wlr keyboard group.");
		goto cleanup;
	}

	nedm_group->wlr_group->data = nedm_group;
	wlr_keyboard_set_keymap(&nedm_group->wlr_group->keyboard,
	                        wlr_keyboard->keymap);
	wlr_keyboard_set_repeat_info(&nedm_group->wlr_group->keyboard,
	                             wlr_keyboard->repeat_info.rate,
	                             wlr_keyboard->repeat_info.delay);
	if(input_device->identifier != NULL) {
		nedm_group->identifier = strdup(input_device->identifier);
	} else {
		nedm_group->identifier = NULL;
	}
	wlr_log(WLR_DEBUG, "Created keyboard group.");

	wlr_keyboard_group_add_keyboard(nedm_group->wlr_group, wlr_keyboard);
	wl_list_insert(&seat->keyboard_groups, &nedm_group->link);

	wl_signal_add(&nedm_group->wlr_group->keyboard.events.key, &nedm_group->key);
	nedm_group->key.notify = handle_keyboard_group_key;

	wl_signal_add(&nedm_group->wlr_group->keyboard.events.modifiers,
	              &nedm_group->modifiers);

	nedm_group->key_repeat_timer = wl_event_loop_add_timer(
	    seat->server->event_loop, handle_keyboard_repeat, nedm_group);

	nedm_group->modifiers.notify = handle_keyboard_group_modifiers;
	nedm_group->enable_keybindings = true;
	nedm_input_manager_configure_keyboard_group(nedm_group);
	return;

cleanup:
	if(nedm_group && nedm_group->wlr_group) {
		wlr_keyboard_group_destroy(nedm_group->wlr_group);
	}
	if(nedm_group && nedm_group->identifier) {
		free(nedm_group->identifier);
	}
	free(nedm_group);
}

static void
new_keyboard(struct nedm_seat *seat, struct nedm_input_device *input_device) {
	struct wlr_input_device *device = input_device->wlr_device;
	struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
	if(!context) {
		wlr_log(WLR_ERROR, "Unable to create XKB context");
		return;
	}

	struct xkb_keymap *keymap =
	    xkb_map_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
	if(!keymap) {
		wlr_log(WLR_ERROR,
		        "Unable to configure keyboard: keymap does not exist");
		xkb_context_unref(context);
		return;
	}

	wlr_keyboard_set_keymap(wlr_keyboard_from_input_device(device), keymap);

	xkb_keymap_unref(keymap);
	xkb_context_unref(context);

	nedm_keyboard_group_add(input_device, seat);
	++seat->num_keyboards;

	wlr_seat_set_keyboard(seat->seat, wlr_keyboard_from_input_device(device));
}

void
seat_add_device(struct nedm_seat *seat, struct nedm_input_device *device) {
	switch(device->wlr_device->type) {
	case WLR_INPUT_DEVICE_KEYBOARD:
		new_keyboard(seat, device);
		break;
	case WLR_INPUT_DEVICE_POINTER:
		new_pointer(seat, device);
		break;
	case WLR_INPUT_DEVICE_TOUCH:
		new_touch(seat, device);
		break;
	case WLR_INPUT_DEVICE_SWITCH:
		wlr_log(WLR_DEBUG, "Switch input is not implemented");
		return;
	case WLR_INPUT_DEVICE_TABLET:
	case WLR_INPUT_DEVICE_TABLET_PAD:
		wlr_log(WLR_DEBUG, "Tablet input is not implemented");
		return;
	}

	update_capabilities(seat);
}

static void
destroy_empty_wlr_keyboard_group(void *data) {
	wlr_keyboard_group_destroy(data);
}

void
remove_keyboard(struct nedm_seat *seat, struct nedm_input_device *keyboard) {
	if(!keyboard) {
		return;
	}
	struct wlr_seat *wlr_seat = seat->seat;
	struct wlr_keyboard *wlr_keyboard =
	    wlr_keyboard_from_input_device(keyboard->wlr_device);
	struct wlr_keyboard_group *wlr_group = wlr_keyboard->group;
	if(wlr_group) {
		wlr_keyboard_group_remove_keyboard(wlr_group, wlr_keyboard);

		if(wl_list_empty(&wlr_group->devices)) {
			wlr_log(WLR_DEBUG, "Destroying empty keyboard group %p",
			        (void *)wlr_group);
			struct nedm_keyboard_group *group = wlr_group->data;
			if(wlr_seat_get_keyboard(wlr_seat) == &wlr_group->keyboard) {
				wlr_seat_set_keyboard(wlr_seat, NULL);
			}
			wlr_group->data = NULL;
			wl_list_remove(&group->link);
			wl_list_remove(&group->key.link);
			wl_list_remove(&group->modifiers.link);
			wl_event_source_remove(group->key_repeat_timer);
			if(group->identifier != NULL) {
				free(group->identifier);
			}
			free(group);

			// To prevent use-after-free conditions when handling key events,
			// defer freeing the wlr_keyboard_group until idle
			wl_event_loop_add_idle(seat->server->event_loop,
			                       destroy_empty_wlr_keyboard_group, wlr_group);
		}
	}

	--seat->num_keyboards;
}

void
seat_remove_device(struct nedm_seat *seat, struct nedm_input_device *device) {
	switch(device->wlr_device->type) {
	case WLR_INPUT_DEVICE_KEYBOARD:
		remove_keyboard(seat, device);
		break;
	case WLR_INPUT_DEVICE_POINTER:
		remove_pointer(seat, device->pointer);
		device->pointer = NULL;
		break;
	case WLR_INPUT_DEVICE_TOUCH:
		remove_touch(seat, device->touch);
		device->touch = NULL;
		break;
	case WLR_INPUT_DEVICE_SWITCH:
		wlr_log(WLR_DEBUG, "Switch input is not implemented");
		return;
	case WLR_INPUT_DEVICE_TABLET:
	case WLR_INPUT_DEVICE_TABLET_PAD:
		wlr_log(WLR_DEBUG, "Tablet input is not implemented");
		return;
	}

	update_capabilities(seat);
}

static void
handle_request_set_primary_selection(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat =
	    wl_container_of(listener, seat, request_set_primary_selection);
	struct wlr_seat_request_set_primary_selection_event *event = data;

	wlr_seat_set_primary_selection(seat->seat, event->source, event->serial);
}

static void
handle_request_set_selection(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat =
	    wl_container_of(listener, seat, request_set_selection);
	struct wlr_seat_request_set_selection_event *event = data;

	wlr_seat_set_selection(seat->seat, event->source, event->serial);
}

static void
handle_request_set_cursor(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, request_set_cursor);
	if(seat->enable_cursor == false ||
	   seat->server->modecursors[seat->mode] != NULL) {
		return;
	}
	struct wlr_seat_pointer_request_set_cursor_event *event = data;
	struct wlr_surface *focused_surface =
	    event->seat_client->seat->pointer_state.focused_surface;
	bool has_focused =
	    focused_surface != NULL && focused_surface->resource != NULL;
	struct wl_client *focused_client = NULL;
	if(has_focused) {
		focused_client = wl_resource_get_client(focused_surface->resource);
	}

	/* This can be sent by any client, so we check to make sure
	 * this one actually has pointer focus first. */
	if(focused_client == event->seat_client->client) {
		wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x,
		                       event->hotspot_y);
	}
}

static void
handle_touch_down(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, touch_down);
	struct wlr_touch_down_event *event = data;

	double lx, ly;
	wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base,
	                                     event->x, event->y, &lx, &ly);

	double sx, sy;
	struct wlr_scene_node *node =
	    wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, &sx, &sy);

	uint32_t serial = 0;
	if(node && node->type == WLR_SCENE_NODE_BUFFER) {
		struct wlr_scene_surface *scene_surface =
		    wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node));
		if(scene_surface != NULL) {
			serial = wlr_seat_touch_notify_down(
			    seat->seat, scene_surface->surface, event->time_msec,
			    event->touch_id, sx, sy);
		}
	}

	if(serial && wlr_seat_touch_num_points(seat->seat) == 1) {
		seat->touch_id = event->touch_id;
		seat->touch_lx = lx;
		seat->touch_ly = ly;
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_touch_up(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, touch_up);
	struct wlr_touch_up_event *event = data;

	if(!wlr_seat_touch_get_point(seat->seat, event->touch_id)) {
		return;
	}

	wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id);
	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_touch_motion(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, touch_motion);
	struct wlr_touch_motion_event *event = data;

	if(!wlr_seat_touch_get_point(seat->seat, event->touch_id)) {
		return;
	}

	double lx, ly;
	wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base,
	                                     event->x, event->y, &lx, &ly);

	double sx, sy;
	struct wlr_scene_node *node =
	    wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, &sx, &sy);

	if(node && node->type == WLR_SCENE_NODE_BUFFER) {
		struct wlr_scene_surface *scene_surface =
		    wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node));
		if(scene_surface != NULL) {

			wlr_seat_touch_point_focus(seat->seat, scene_surface->surface,
			                           event->time_msec, event->touch_id, sx,
			                           sy);
		}
		wlr_seat_touch_notify_motion(seat->seat, event->time_msec,
		                             event->touch_id, sx, sy);
	} else {
		wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec,
		                                 event->touch_id);
	}

	if(event->touch_id == seat->touch_id) {
		seat->touch_lx = lx;
		seat->touch_ly = ly;
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_cursor_frame(struct wl_listener *listener,
                    __attribute__((unused)) void *_data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, cursor_frame);

	wlr_seat_pointer_notify_frame(seat->seat);
	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_cursor_axis(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, cursor_axis);
	struct wlr_pointer_axis_event *event = data;

	wlr_seat_pointer_notify_axis(
	    seat->seat, event->time_msec, event->orientation, event->delta,
	    event->delta_discrete, event->source, event->relative_direction);
	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_cursor_button(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, cursor_button);
	struct wlr_pointer_button_event *event = data;

	wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button,
	                               event->state);

	if(event->state == WL_POINTER_BUTTON_STATE_PRESSED) {
		double sx, sy;
		struct wlr_scene_node *node = wlr_scene_node_at(
		    &seat->server->scene->tree.node, seat->cursor->x, seat->cursor->y,
		    &sx, &sy);
		if(node && node->type == WLR_SCENE_NODE_BUFFER) {
			struct wlr_scene_surface *scene_surface =
			    wlr_scene_surface_try_from_buffer(
			        wlr_scene_buffer_from_node(node));
			if(scene_surface) {
				struct nedm_view *view = view_from_wlr_surface(
				    seat->server, scene_surface->surface);
				if(view) {
					seat_set_focus(seat, view);
				}
			}
		}
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
process_cursor_motion(struct nedm_seat *seat, uint32_t time) {
	double sx, sy;
	struct wlr_seat *wlr_seat = seat->seat;
	struct wlr_surface *surface = NULL;

	struct wlr_scene_node *node =
	    wlr_scene_node_at(&seat->server->scene->tree.node, seat->cursor->x,
	                      seat->cursor->y, &sx, &sy);

	if(node && node->type == WLR_SCENE_NODE_BUFFER) {
		struct wlr_scene_surface *scene_surface =
		    wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node));
		if(scene_surface != NULL) {
			surface = scene_surface->surface;
		}
	}

	bool skip_update = false;
	if(seat->active_constraint) {
		struct wlr_pointer_constraint_v1 *constraint = seat->active_constraint;
		if(constraint->surface != surface) {
			wlr_pointer_constraint_v1_send_deactivated(constraint);
			wl_list_remove(&seat->constraint_destroy.link);
			seat->active_constraint = NULL;
		} else if(constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) {
			// For locked pointer, do not move or warp the cursor, just notify enter/motion
			if(surface) {
				wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy);
				if(time > 0) {
					wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy);
				}
			}
			// Do not warp or clamp the cursor for locked pointer
			skip_update = true;
		}
	}

	if(!skip_update) {
		if(surface) {
			bool focus_changed =
			    wlr_seat->pointer_state.focused_surface != surface;
			if(!focus_changed && time > 0) {
				wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy);
			}
			wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy);
		} else {
			wlr_seat_pointer_clear_focus(wlr_seat);
		}
	}

	if(seat->active_constraint &&
	   seat->active_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) {
		struct wlr_box surface_box;
		wlr_surface_get_extents(seat->active_constraint->surface, &surface_box);
		double sx = seat->cursor->x - surface_box.x;
		double sy = seat->cursor->y - surface_box.y;

		if(sx < 0) {
			sx = 0;
		}
		if(sy < 0) {
			sy = 0;
		}
		if(sx > surface_box.width) {
			sx = surface_box.width;
		}
		if(sy > surface_box.height) {
			sy = surface_box.height;
		}

		wlr_cursor_warp(seat->cursor, NULL, surface_box.x + sx,
		                surface_box.y + sy);
	}

	struct nedm_drag_icon *drag_icon;
	wl_list_for_each(drag_icon, &seat->drag_icons, link) {
		drag_icon_update_position(drag_icon);
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);

	/* Check if cursor switched tile */
	struct wlr_output *c_outp = wlr_output_layout_output_at(
	    seat->server->output_layout, seat->cursor->x, seat->cursor->y);
	struct nedm_output *nedm_outp = NULL;
	wl_list_for_each(nedm_outp, &seat->server->outputs, link) {
		if(nedm_outp->wlr_output == c_outp) {
			break;
		}
	}
	if(c_outp && nedm_outp->workspaces &&
	   nedm_outp->curr_workspace < nedm_outp->server->nws &&
	   nedm_outp->workspaces[nedm_outp->curr_workspace] &&
	   nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile) {
		struct nedm_tile *c_tile;
		bool first = true;
		for(c_tile = nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile;
		    first ||
		    c_tile !=
		        nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile;
		    c_tile = c_tile->next) {
			first = false;
			double ox = seat->cursor->x, oy = seat->cursor->y;
			wlr_output_layout_output_coords(seat->server->output_layout, c_outp,
			                                &ox, &oy);
			if(c_tile->tile.x <= ox && c_tile->tile.y <= oy &&
			   c_tile->tile.x + c_tile->tile.width >= ox &&
			   c_tile->tile.y + c_tile->tile.height >= oy) {
				break;
			}
		}
		if(seat->cursor_tile != NULL && seat->cursor_tile != c_tile &&
		   seat->server->running) {
			ipc_send_event(
			    seat->server,
			    "{'event_name':'cursor_switch_tile','old_output':'%s','old_output_id':%d,'old_tile':%d,'new_output':'%s','new_output_id':%d,'new_tile':%d}",
			    seat->cursor_tile->workspace->output->name,
			    output_get_num(seat->cursor_tile->workspace->output),
			    seat->cursor_tile->id, c_outp->name, output_get_num(nedm_outp),
			    c_tile->id);
		}
		seat->cursor_tile = c_tile;
	}
}

static void
handle_cursor_motion_absolute(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat =
	    wl_container_of(listener, seat, cursor_motion_absolute);
	struct wlr_pointer_motion_absolute_event *event = data;

	wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x,
	                         event->y);
	process_cursor_motion(seat, event->time_msec);
	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
handle_cursor_motion(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, cursor_motion);
	struct wlr_pointer_motion_event *event = data;

	// If pointer is locked, do not move the visible cursor, only send relative motion
	if (seat->active_constraint && seat->active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) {
		// Only send relative motion to the client
		if (seat->server->relative_pointer_manager) {
			wlr_relative_pointer_manager_v1_send_relative_motion(
				seat->server->relative_pointer_manager, seat->seat,
				(uint64_t)event->time_msec * 1000, event->delta_x, event->delta_y,
				event->unaccel_dx, event->unaccel_dy);
		}
		// Notify the surface of motion (for button state, etc)
		process_cursor_motion(seat, event->time_msec);
		wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
		return;
	}

	wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x,
	                event->delta_y);
	process_cursor_motion(seat, event->time_msec);

	// Send relative motion AFTER cursor position is updated
	if (seat->server->relative_pointer_manager) {
		wlr_relative_pointer_manager_v1_send_relative_motion(
			seat->server->relative_pointer_manager, seat->seat,
			(uint64_t)event->time_msec * 1000, event->delta_x, event->delta_y,
			event->unaccel_dx, event->unaccel_dy);
	}

	wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
drag_icon_update_position(struct nedm_drag_icon *drag_icon) {
	struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon;
	struct nedm_seat *seat = drag_icon->seat;
	struct wlr_touch_point *point;

	switch(wlr_icon->drag->grab_type) {
	case WLR_DRAG_GRAB_KEYBOARD:
		return;
	case WLR_DRAG_GRAB_KEYBOARD_POINTER:
		drag_icon->lx = seat->cursor->x;
		drag_icon->ly = seat->cursor->y;
		break;
	case WLR_DRAG_GRAB_KEYBOARD_TOUCH:
		point = wlr_seat_touch_get_point(seat->seat, wlr_icon->drag->touch_id);
		if(!point) {
			return;
		}
		drag_icon->lx = seat->touch_lx;
		drag_icon->ly = seat->touch_ly;
		break;
	}
	wlr_scene_node_set_position(&drag_icon->scene_tree->node, drag_icon->lx,
	                            drag_icon->ly);
}

static void
handle_drag_icon_destroy(struct wl_listener *listener,
                         __attribute__((unused)) void *_data) {
	struct nedm_drag_icon *drag_icon =
	    wl_container_of(listener, drag_icon, destroy);

	wl_list_remove(&drag_icon->link);
	wl_list_remove(&drag_icon->destroy.link);
	wlr_scene_node_destroy(&drag_icon->scene_tree->node);
	free(drag_icon);
}

static void
handle_request_start_drag(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, request_start_drag);
	struct wlr_seat_request_start_drag_event *event = data;

	if(wlr_seat_validate_pointer_grab_serial(seat->seat, event->origin,
	                                         event->serial)) {
		wlr_seat_start_pointer_drag(seat->seat, event->drag, event->serial);
		return;
	}

	struct wlr_touch_point *point;
	if(wlr_seat_validate_touch_grab_serial(seat->seat, event->origin,
	                                       event->serial, &point)) {
		wlr_seat_start_touch_drag(seat->seat, event->drag, event->serial,
		                          point);
		return;
	}

	wlr_log(WLR_DEBUG,
	        "Ignoring start_drag request: "
	        "could not validate pointer/touch serial %" PRIu32,
	        event->serial);
	wlr_data_source_destroy(event->drag->source);
}

static void
handle_start_drag(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, start_drag);
	struct wlr_drag *wlr_drag = data;
	struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon;
	if(wlr_drag_icon == NULL) {
		return;
	}

	struct nedm_drag_icon *drag_icon = calloc(1, sizeof(struct nedm_drag_icon));
	if(!drag_icon) {
		return;
	}
	drag_icon->seat = seat;
	drag_icon->wlr_drag_icon = wlr_drag_icon;
	drag_icon->scene_tree = wlr_scene_subsurface_tree_create(
	    &seat->server->scene->tree, wlr_drag_icon->surface);
	if(!drag_icon->scene_tree) {
		free(drag_icon);
		return;
	}

	drag_icon->destroy.notify = handle_drag_icon_destroy;
	wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy);

	wl_list_insert(&seat->drag_icons, &drag_icon->link);

	drag_icon_update_position(drag_icon);
}

static void
handle_constraint_destroy(struct wl_listener *listener,
                          __attribute__((unused)) void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, constraint_destroy);
	seat->active_constraint = NULL;
	wl_list_remove(&seat->constraint_destroy.link);
}

static void
handle_new_constraint(struct wl_listener *listener, void *data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, new_constraint);
	struct wlr_pointer_constraint_v1 *constraint = data;

	// If there's an existing constraint, destroy it
	if (seat->active_constraint) {
		wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint);
		wl_list_remove(&seat->constraint_destroy.link);
		seat->active_constraint = NULL;
	}

	seat->active_constraint = constraint;
	wl_signal_add(&constraint->events.destroy, &seat->constraint_destroy);
	seat->constraint_destroy.notify = handle_constraint_destroy;

	// Activate the constraint
	wlr_pointer_constraint_v1_send_activated(constraint);
	process_cursor_motion(seat, -1);
}

static void
handle_destroy(struct wl_listener *listener,
               __attribute__((unused)) void *_data) {
	struct nedm_seat *seat = wl_container_of(listener, seat, destroy);
	wl_list_remove(&seat->destroy.link);

	struct nedm_keyboard_group *group, *group_tmp;
	wl_list_for_each_safe(group, group_tmp, &seat->keyboard_groups, link) {
		wl_list_remove(&group->link);
		wl_list_remove(&group->key.link);
		wl_list_remove(&group->modifiers.link);
		wl_event_source_remove(group->key_repeat_timer);
		wlr_keyboard_group_destroy(group->wlr_group);
		if(group->identifier) {
			free(group->identifier);
		}
		free(group);
	}

	struct nedm_input_device *it, *it_tmp;
	wl_list_for_each_safe(it, it_tmp, &seat->server->input->devices, link) {
		input_manager_handle_device_destroy(&it->device_destroy, NULL);
	}

	wlr_xcursor_manager_destroy(seat->xcursor_manager);
	wl_list_remove(&seat->cursor_motion.link);
	wl_list_remove(&seat->cursor_motion_absolute.link);
	wl_list_remove(&seat->cursor_button.link);
	wl_list_remove(&seat->cursor_axis.link);
	wl_list_remove(&seat->cursor_frame.link);
	wl_list_remove(&seat->touch_down.link);
	wl_list_remove(&seat->touch_up.link);
	wl_list_remove(&seat->touch_motion.link);
	wl_list_remove(&seat->request_set_cursor.link);
	wl_list_remove(&seat->request_set_selection.link);
	wl_list_remove(&seat->request_set_primary_selection.link);
	if(seat->cursor) {
		wlr_cursor_destroy(seat->cursor);
	}
	seat->server->seat = NULL;
	free(seat);
}

struct nedm_seat *
seat_create(struct nedm_server *server) {
	struct nedm_seat *seat = calloc(1, sizeof(struct nedm_seat));
	if(!seat) {
		wlr_log(WLR_ERROR, "Cannot allocate seat");
		return NULL;
	}

	seat->enable_cursor = true;
	seat->seat = wlr_seat_create(server->wl_display, "seat0");
	if(!seat->seat) {
		wlr_log(WLR_ERROR, "Cannot allocate seat0");
		free(seat);
		return NULL;
	}
	seat->server = server;
	seat->destroy.notify = handle_destroy;
	wl_signal_add(&seat->seat->events.destroy, &seat->destroy);

	seat->cursor = wlr_cursor_create();
	if(!seat->cursor) {
		wlr_log(WLR_ERROR, "Unable to create cursor");
		wl_list_remove(&seat->destroy.link);
		free(seat);
		return NULL;
	}
	wlr_cursor_attach_output_layout(seat->cursor, server->output_layout);

	if(!seat->xcursor_manager) {
		seat->xcursor_manager =
		    wlr_xcursor_manager_create(NULL, server->xcursor_size);
		if(!seat->xcursor_manager) {
			wlr_log(WLR_ERROR, "Cannot create XCursor manager");
			wlr_cursor_destroy(seat->cursor);
			wl_list_remove(&seat->destroy.link);
			free(seat);
			return NULL;
		}
	}

	seat->cursor_motion.notify = handle_cursor_motion;
	wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion);
	seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute;
	wl_signal_add(&seat->cursor->events.motion_absolute,
	              &seat->cursor_motion_absolute);
	seat->cursor_button.notify = handle_cursor_button;
	wl_signal_add(&seat->cursor->events.button, &seat->cursor_button);
	seat->cursor_axis.notify = handle_cursor_axis;
	wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis);
	seat->cursor_frame.notify = handle_cursor_frame;
	wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame);

	seat->touch_down.notify = handle_touch_down;
	wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down);
	seat->touch_up.notify = handle_touch_up;
	wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up);
	seat->touch_motion.notify = handle_touch_motion;
	wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion);

	seat->request_set_cursor.notify = handle_request_set_cursor;
	wl_signal_add(&seat->seat->events.request_set_cursor,
	              &seat->request_set_cursor);
	seat->request_set_selection.notify = handle_request_set_selection;
	wl_signal_add(&seat->seat->events.request_set_selection,
	              &seat->request_set_selection);
	seat->request_set_primary_selection.notify =
	    handle_request_set_primary_selection;
	wl_signal_add(&seat->seat->events.request_set_primary_selection,
	              &seat->request_set_primary_selection);

	seat->new_constraint.notify = handle_new_constraint;
	wl_signal_add(&server->pointer_constraints->events.new_constraint,
	              &seat->new_constraint);

	wl_list_init(&seat->keyboard_groups);
	seat->num_keyboards = 0;
	seat->num_pointers = 0;
	seat->num_touch = 0;

	wl_list_init(&seat->drag_icons);
	seat->request_start_drag.notify = handle_request_start_drag;
	wl_signal_add(&seat->seat->events.request_start_drag,
	              &seat->request_start_drag);
	seat->start_drag.notify = handle_start_drag;
	wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag);

	seat->mode = 0;
	seat->default_mode = 0;
	seat->active_constraint = NULL;

	return seat;
}

void
seat_destroy(struct nedm_seat *seat) {
	if(!seat) {
		return;
	}

	// Clean up active constraint
	if (seat->active_constraint) {
		wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint);
		wl_list_remove(&seat->constraint_destroy.link);
		seat->active_constraint = NULL;
	}

	wl_list_remove(&seat->new_constraint.link);
	wl_list_remove(&seat->request_start_drag.link);
	wl_list_remove(&seat->start_drag.link);

	// Destroying the wlr seat will trigger the destroy handler on our seat,
	// which will in turn free it.
	wlr_seat_destroy(seat->seat);
}

/* Important: this function returns NULL if we are focused on the background */
struct nedm_view *
seat_get_focus(const struct nedm_seat *seat) {
	return seat->focused_view;
}

void
seat_set_focus(struct nedm_seat *seat, struct nedm_view *view) {
	struct nedm_server *server = seat->server;
	struct wlr_seat *wlr_seat = seat->seat;
	struct nedm_view *prev_view = seat_get_focus(seat);

	// Clear any active pointer constraint when focus changes
	if (seat->active_constraint) {
		wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint);
		wl_list_remove(&seat->constraint_destroy.link);
		seat->active_constraint = NULL;
	}

	/* Focusing the background */
	if(view == NULL) {
		if(server->curr_output && server->curr_output->workspaces &&
		   server->curr_output->curr_workspace < server->nws &&
		   server->curr_output->workspaces[server->curr_output->curr_workspace] &&
		   server->curr_output->workspaces[server->curr_output->curr_workspace]->focused_tile) {
			workspace_tile_update_view(
			    server->curr_output->workspaces[server->curr_output->curr_workspace]
			        ->focused_tile,
			    NULL);
		}
		seat->focused_view = NULL;
		if(prev_view != NULL) {
			view_activate(prev_view, false);
		}
		wlr_seat_keyboard_clear_focus(wlr_seat);
		process_cursor_motion(seat, -1);
		return;
	}

#if NEDM_HAS_XWAYLAND
	if(view->type != NEDM_XWAYLAND_VIEW || xwayland_view_should_manage(view))
#endif
	{
		struct nedm_workspace *curr_workspace =
		    server->curr_output
		        ->workspaces[server->curr_output->curr_workspace];
		workspace_tile_update_view(curr_workspace->focused_tile, view);
		wl_list_remove(&curr_workspace->views);
		wl_list_insert(&view->link, &curr_workspace->views);
	}

#if NEDM_HAS_XWAYLAND
	if(view->type == NEDM_XWAYLAND_VIEW) {
		wlr_xwayland_set_seat(server->xwayland, seat->seat);
	}
	if(view->type == NEDM_XWAYLAND_VIEW && !xwayland_view_should_manage(view)) {
		const struct nedm_xwayland_view *xwayland_view =
		    xwayland_view_from_view(view);
		if(!wlr_xwayland_surface_override_redirect_wants_focus(
		       xwayland_view->xwayland_surface)) {
			return;
		}
	} else
#endif
	{
		if(prev_view != NULL) {
			view_activate(prev_view, false);
		}

		seat->focused_view = view;
	}

	view_activate(view, true);

	struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat);
	wlr_seat_keyboard_end_grab(wlr_seat);
	if(keyboard) {
		wlr_seat_keyboard_notify_enter(
		    wlr_seat, view->wlr_surface, keyboard->keycodes,
		    keyboard->num_keycodes, &keyboard->modifiers);
	} else {
		wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0,
		                               NULL);
	}

	wlr_scene_node_raise_to_top(&view->scene_tree->node);
	process_cursor_motion(seat, -1);
	wlr_scene_node_set_position(
	    &view->workspace->output->bg->node,
	    output_get_layout_box(view->workspace->output).x,
	    output_get_layout_box(view->workspace->output).y);
}
