// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include <assert.h>
#include <float.h>
#include <stdlib.h>

#include <wlr/types/wlr_compositor.h>
#include <wlr/util/region.h>

#include "input/event.h"
#include "input/seat.h"
#include "output.h"
#include "painter.h"
#include "scene/surface.h"
#include "theme.h"
#include "util/macros.h"
#include "view/action.h"
#include "xwayland_p.h"

struct xwayland_view {
    struct view view;
    struct wl_list link;
    struct xwayland_server *xwayland;
    struct wlr_xwayland_surface *wlr_xwayland_surface;
    struct wl_listener surface_tree_destroy;

    bool minimizable, maximizable;
    bool focusable, activatable, movable, resizable;
    struct wl_listener view_update_capabilities;

    struct wl_listener client_commit;
    struct wl_listener commit;

    struct wl_listener associate;
    struct wl_listener dissociate;
    struct wl_listener map;
    struct wl_listener unmap;
    struct wl_listener destroy;

    struct wl_listener request_configure;
    struct wl_listener request_move;
    struct wl_listener request_resize;
    struct wl_listener request_minimize;
    struct wl_listener request_maximize;
    struct wl_listener request_fullscreen;
    struct wl_listener request_activate;
    struct wl_listener request_sticky;
    struct wl_listener request_skip_taskbar;
    struct wl_listener request_above;
    struct wl_listener request_below;
    struct wl_listener request_demands_attention;
    struct wl_listener request_modal;

    /* view state need sync with xwayland */
    struct wl_listener view_above;
    struct wl_listener view_below;
    struct wl_listener view_sticky;
    struct wl_listener view_skip_taskbar;
    struct wl_listener view_demands_attention;

    struct wl_listener set_title;
    struct wl_listener set_class;
    // struct wl_listener set_role;
    struct wl_listener set_parent;
    // struct wl_listener set_startup_id;
    // struct wl_listener set_window_type;
    struct wl_listener set_hints;
    struct wl_listener set_size_hints;
    struct wl_listener set_functions;
    struct wl_listener set_decorations;
    struct wl_listener set_strut_partial;
    struct wl_listener set_override_redirect;
    // struct wl_listener set_geometry;
    struct wl_listener ping_timeout;

    struct wl_list net_wm_icons; // from net_wm_icon

    struct {
        xcb_sync_counter_t counter;
        xcb_sync_int64_t value;
        xcb_sync_alarm_t alarm;
        bool blocked;
    } sync;
};

struct net_wm_icon {
    struct wl_list link;

    uint32_t width, height;
    unsigned char *data;
    struct wlr_buffer *buffer;
};

static bool xwayland_view_hover(struct seat *seat, struct ky_scene_node *node, double x, double y,
                                uint32_t time, bool first, bool hold, void *data)
{
    struct wlr_surface *surface = wlr_surface_try_from_node(node);
    struct xwayland_view *xwayland_view = data;

    if (first) {
        view_hover(seat, &xwayland_view->view);
    }

    xwayland_update_seat(seat);
    xwayland_update_hovered_surface(surface);

    if (!hold) {
        seat_notify_motion(seat, surface, time, xwayland_scale(x), xwayland_scale(y), first);
        return false;
    }

    double sx = x - xwayland_view->view.base.geometry.x;
    double sy = y - xwayland_view->view.base.geometry.y;
    seat_notify_motion(seat, surface, time, xwayland_scale(sx), xwayland_scale(sy), first);
    return true;
}

static void xwayland_view_click(struct seat *seat, struct ky_scene_node *node, uint32_t button,
                                bool pressed, uint32_t time, enum click_state state, void *data)
{
    xwayland_update_seat(seat);

    seat_notify_button(seat, time, button, pressed);

    /* only do activated when button pressed */
    if (!pressed) {
        return;
    }

    struct xwayland_view *xwayland_view = data;
    view_click(seat, &xwayland_view->view, button, pressed, state);
}

static void xwayland_view_leave(struct seat *seat, struct ky_scene_node *node, bool last,
                                void *data)
{
    /* so surface will call set_cursor when enter again */
    struct wlr_surface *surface = wlr_surface_try_from_node(node);
    seat_notify_leave(seat, surface);
}

static struct ky_scene_node *xwayland_view_get_root(void *data)
{
    struct xwayland_view *xwayland_view = data;
    return &xwayland_view->view.tree->node;
}

static struct wlr_surface *xwayland_view_get_toplevel(void *data)
{
    struct xwayland_view *xwayland_view = data;
    return xwayland_view->view.surface;
}

static const struct input_event_node_impl xwayland_view_event_node_impl = {
    .hover = xwayland_view_hover,
    .click = xwayland_view_click,
    .leave = xwayland_view_leave,
};

static struct xwayland_view *xwayland_view_from_view(struct view *view)
{
    struct xwayland_view *xwayland_view = wl_container_of(view, xwayland_view, view);
    return xwayland_view;
}

static void xwayland_view_close(struct view *view)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    wlr_xwayland_surface_close(xwayland_view->wlr_xwayland_surface);
}

static void xwayland_view_destroy(struct view *view)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    free(xwayland_view);
}

static void xwayland_view_move(struct xwayland_view *xwayland_view, int x, int y)
{
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct view *view = &xwayland_view->view;

    /* xwayland views need sync position except in moving */
    if (!view->interactive_moving) {
        wlr_xwayland_surface_configure(wlr_xwayland_surface, xwayland_scale(x), xwayland_scale(y),
                                       wlr_xwayland_surface->width, wlr_xwayland_surface->height);
    }
    view_helper_move(view, x, y);
}

static void xwayland_restack_view(struct xwayland_view *xwayland_view)
{
    struct wlr_xwayland_surface *surface = xwayland_view->wlr_xwayland_surface;

    if (xwayland_view->view.base.kept_below) {
        wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_BELOW);
        return;
    } else if (xwayland_view->view.base.kept_above) {
        wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE);
        xwayland_restack_unmanaged(xwayland_view->xwayland);
        return;
    }

    wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE);

    struct xwayland_view *view;
    wl_list_for_each(view, &xwayland_view->xwayland->surfaces, link) {
        surface = view->wlr_xwayland_surface;
        if (xwayland_view->view.base.kept_above) {
            wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE);
        }
    }

    xwayland_restack_unmanaged(xwayland_view->xwayland);
}

static void xwayland_view_ping(struct view *view)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    wlr_xwayland_surface_ping(wlr_xwayland_surface);
}

static void xwayland_view_configure(struct view *view)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;
    bool fixup_pointer_position = false;

    if (view->pending.action & VIEW_ACTION_MINIMIZE) {
        view->pending.action &= ~VIEW_ACTION_MINIMIZE;
        fixup_pointer_position = !kywc_view->minimized;
        wlr_xwayland_surface_set_minimized(wlr_xwayland_surface, kywc_view->minimized);
    }

    if (view->pending.action & VIEW_ACTION_ACTIVATE) {
        view->pending.action &= ~VIEW_ACTION_ACTIVATE;
        if (kywc_view->activated && wlr_xwayland_surface->minimized) {
            fixup_pointer_position = true;
            wlr_xwayland_surface_set_minimized(wlr_xwayland_surface, false);
        }
        if (kywc_view->activated) {
            wlr_xwayland_surface_activate(wlr_xwayland_surface, true);
            xwayland_view->xwayland->activated_surface = wlr_xwayland_surface;
            xwayland_restack_view(xwayland_view);
        }
    }

    if (fixup_pointer_position) {
        xwayland_fixup_pointer_position(wlr_xwayland_surface->surface);
    }

    /* direct move when not changed size */
    if (view->pending.action & VIEW_ACTION_MOVE) {
        view->pending.action &= ~VIEW_ACTION_MOVE;
        if (!view_action_change_size(view->pending.configure_action)) {
            xwayland_view_move(xwayland_view, view->pending.geometry.x, view->pending.geometry.y);
        } else {
            kywc_log(KYWC_DEBUG, "Skip move when pending configure action 0x%x",
                     view->pending.configure_action);
        }
    }

    if (view->pending.action == VIEW_ACTION_NOP) {
        return;
    }

    /* now, only changed size action left */
    assert(view_action_change_size(view->pending.action));

    if (view->pending.action & VIEW_ACTION_FULLSCREEN) {
        wlr_xwayland_surface_set_fullscreen(wlr_xwayland_surface, kywc_view->fullscreen);
    }

    if (view->pending.action & VIEW_ACTION_MAXIMIZE) {
        wlr_xwayland_surface_set_maximized(wlr_xwayland_surface, kywc_view->maximized);
    }

    struct kywc_box *pending = &view->pending.geometry;

    /* If no need to resizing, process the move immediately */
    if (!view_action_change_size(view->pending.configure_action) &&
        wlr_xwayland_surface->width == xwayland_scale(pending->width) &&
        wlr_xwayland_surface->height == xwayland_scale(pending->height)) {
        view->pending.action &= ~VIEW_ACTION_RESIZE;
        xwayland_view_move(xwayland_view, pending->x, pending->y);
        view_configure(&xwayland_view->view, 0);
        view_configured(&xwayland_view->view, true);
        return;
    }

    /* there is no commit after map */
    if (view->base.has_initial_position) {
        view_helper_move(view, pending->x, pending->y);
    }

    if (xwayland_view->sync.counter) {
        /* skip the configure if prev one is not acked when resizing */
        if (xwayland_view->sync.blocked && view->current_resize_edges != KYWC_EDGE_NONE) {
            view_configure(&xwayland_view->view, xwayland_view->sync.counter);
            return;
        }
        if (!xwayland_view->sync.blocked) {
            xwayland_set_allow_commits(wlr_xwayland_surface->window_id, false);
            xwayland_view->sync.blocked = true;
        }
        xwayland_send_sync_request(wlr_xwayland_surface->window_id, &xwayland_view->sync.value);
    }

    wlr_xwayland_surface_configure(wlr_xwayland_surface, xwayland_scale(pending->x),
                                   xwayland_scale(pending->y), xwayland_scale(pending->width),
                                   xwayland_scale(pending->height));
    view_configure(&xwayland_view->view, xwayland_view->sync.counter);
}

static void xwayland_view_configure_timeout(struct view *view)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    assert(xwayland_view->sync.counter);
    /* ack_synced but not committed */
    if (!xwayland_view->sync.blocked) {
        return;
    }
    xwayland_set_allow_commits(wlr_xwayland_surface->window_id, true);
    xwayland_view->sync.blocked = false;
}

static struct wlr_buffer *xwayland_view_get_wm_icon_buffer(struct view *view, int size, float scale)
{
    struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
    struct draw_info info = { .width = size, .height = size, .scale = scale };

    float scale_width = info.width * info.scale;
    float min_abs = FLT_MAX;
    float tmp_abs;
    struct net_wm_icon *icon_similar = NULL;
    struct net_wm_icon *icon;
    wl_list_for_each(icon, &xwayland_view->net_wm_icons, link) {
        tmp_abs = fabs(icon->width - scale_width);
        if (tmp_abs < min_abs) {
            min_abs = tmp_abs;
            icon_similar = icon;
        }
    }
    if (!icon_similar) {
        return NULL;
    }

    if (icon_similar->buffer) {
        return icon_similar->buffer;
    }

    info.pixel.width = icon_similar->width;
    info.pixel.height = icon_similar->height;
    info.pixel.data = icon_similar->data;
    icon_similar->buffer = painter_draw_buffer(&info);

    return icon_similar->buffer;
}

static void xwayland_view_update_usable_area(struct view *view, struct kywc_output *output,
                                             struct kywc_box *usable_area, enum kywc_edges edge)
{
    struct xwayland_view *xwayland_view = wl_container_of(view, xwayland_view, view);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;

    struct kywc_box geo;
    kywc_output_effective_geometry(xwayland_view->view.output, &geo);

    xcb_ewmh_wm_strut_partial_t *strut = wlr_xwayland_surface->strut_partial;
    if (strut->left_start_y != strut->left_end_y) {
        int scaled_left = xwayland_unscale(strut->left);
        geo.x += scaled_left;
        geo.width -= scaled_left;
    }
    if (strut->right_start_y != strut->right_end_y) {
        geo.width -= xwayland_unscale(strut->right);
    }
    if (strut->top_start_x != strut->top_end_x) {
        int scaled_top = xwayland_unscale(strut->top);
        geo.y += scaled_top;
        geo.height -= scaled_top;
    }
    if (strut->bottom_start_x != strut->bottom_end_x) {
        geo.height -= xwayland_unscale(strut->bottom);
    }

    /* intersect usable_area and geo */
    usable_area->x = MAX(geo.x, usable_area->x);
    usable_area->y = MAX(geo.y, usable_area->y);
    usable_area->width = MIN(geo.width, usable_area->width);
    usable_area->height = MIN(geo.height, usable_area->height);
}

static const struct view_impl xwl_surface_impl = {
    .ping = xwayland_view_ping,
    .configure = xwayland_view_configure,
    .close = xwayland_view_close,
    .destroy = xwayland_view_destroy,
    .configure_timeout = xwayland_view_configure_timeout,
    .update_usable_area = xwayland_view_update_usable_area,
    .get_icon_buffer = xwayland_view_get_wm_icon_buffer,
};

static void xwayland_view_update_geometry(struct xwayland_view *xwayland_view)
{
    struct wlr_surface_state *state = &xwayland_view->wlr_xwayland_surface->surface->current;
    xcb_size_hints_t *size_hints = xwayland_view->wlr_xwayland_surface->size_hints;

    if (!size_hints) {
        view_update_size(&xwayland_view->view, state->width, state->height, 0, 0, 0, 0);
    } else {
        /* convert -1 to zero followed by xdg-shell */
        view_update_size(&xwayland_view->view, state->width, state->height,
                         size_hints->min_width < 0 ? 0 : xwayland_unscale(size_hints->min_width),
                         size_hints->min_height < 0 ? 0 : xwayland_unscale(size_hints->min_height),
                         size_hints->max_width < 0 ? 0 : xwayland_unscale(size_hints->max_width),
                         size_hints->max_height < 0 ? 0 : xwayland_unscale(size_hints->max_height));
    }
}

static void xwayland_view_handle_commit(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit);
    struct kywc_box geo = xwayland_view->view.base.geometry;
    struct kywc_box *current = &xwayland_view->view.base.geometry;
    uint32_t resize_edges = xwayland_view->view.current_resize_edges;

    xwayland_view_update_geometry(xwayland_view);

    enum view_action pending_action = xwayland_view->view.pending.configure_action;
    if (pending_action == VIEW_ACTION_NOP) {
        /* fix position when resizing by left or top edges */
        int x = resize_edges & KYWC_EDGE_LEFT ? geo.x + geo.width - current->width : geo.x;
        int y = resize_edges & KYWC_EDGE_TOP ? geo.y + geo.height - current->height : geo.y;
        if (x != geo.x || y != geo.y) {
            xwayland_view_move(xwayland_view, x, y);
        }
        return;
    }

    assert(view_action_change_size(pending_action));

    struct kywc_box *pending = &xwayland_view->view.pending.configure_geometry;
    int x = pending->x, y = pending->y;

    if (pending_action & VIEW_ACTION_RESIZE) {
        if (resize_edges & KYWC_EDGE_LEFT) {
            x += pending->width - current->width;
        }
        if (resize_edges & KYWC_EDGE_TOP) {
            y += pending->height - current->height;
        }
    }

    xwayland_view_move(xwayland_view, x, y);
    /* don't clear configure timer if this commit is handled after block */
    view_configured(&xwayland_view->view, !xwayland_view->sync.blocked);
}

static void xwayland_view_handle_request_move(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_move);
    struct seat *seat = seat_from_wlr_seat(xwayland_view->xwayland->wlr_xwayland->seat);

    if (view_validate_move_or_resize_request(&xwayland_view->view, seat)) {
        window_begin_move(&xwayland_view->view, seat);
    }
}

static void xwayland_view_handle_request_resize(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_resize);
    struct wlr_xwayland_resize_event *event = data;

    struct seat *seat = seat_from_wlr_seat(xwayland_view->xwayland->wlr_xwayland->seat);
    if (view_validate_move_or_resize_request(&xwayland_view->view, seat)) {
        window_begin_resize(&xwayland_view->view, event->edges, seat);
    }
}

static void xwayland_view_handle_request_minimize(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_minimize);
    struct wlr_xwayland_minimize_event *event = data;

    kywc_view_set_minimized(&xwayland_view->view.base, event->minimize);
}

static void xwayland_view_handle_request_maximize(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_maximize);

    bool maximized = xwayland_view->wlr_xwayland_surface->maximized_horz &&
                     xwayland_view->wlr_xwayland_surface->maximized_vert;
    kywc_view_set_maximized(&xwayland_view->view.base, maximized, NULL);
}

static void xwayland_view_handle_request_fullscreen(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_fullscreen);

    bool fullscreen = xwayland_view->wlr_xwayland_surface->fullscreen;
    kywc_view_set_fullscreen(&xwayland_view->view.base, fullscreen, NULL);
}

static void xwayland_view_handle_request_activate(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_activate);

    /* force sync activate surface and focus surface */
    if (xwayland_view->xwayland->activated_surface == xwayland_view->wlr_xwayland_surface) {
        wlr_xwayland_surface_activate(xwayland_view->wlr_xwayland_surface, true);
    }
    kywc_view_activate(&xwayland_view->view.base);

    struct wlr_seat *wlr_seat = xwayland_view->xwayland->wlr_xwayland->seat;
    struct seat *seat = wlr_seat ? seat_from_wlr_seat(wlr_seat) : input_manager_get_default_seat();
    view_set_focus(&xwayland_view->view, seat);
}

static void xwayland_view_handle_request_sticky(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_sticky);

    bool sticky = xwayland_view->wlr_xwayland_surface->sticky;
    kywc_view_set_sticky(&xwayland_view->view.base, sticky);
}

static void xwayland_view_handle_request_skip_taskbar(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_skip_taskbar);

    bool skip_taskbar = xwayland_view->wlr_xwayland_surface->skip_taskbar;
    kywc_view_set_skip_taskbar(&xwayland_view->view.base, skip_taskbar);
}

static void xwayland_view_handle_request_above(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_above);

    bool above = xwayland_view->wlr_xwayland_surface->above;
    kywc_view_set_kept_above(&xwayland_view->view.base, above);
}

static void xwayland_view_handle_request_below(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_below);

    bool below = xwayland_view->wlr_xwayland_surface->below;
    kywc_view_set_kept_below(&xwayland_view->view.base, below);
}

static void xwayland_view_handle_request_demands_attention(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_demands_attention);

    bool demands_attention = xwayland_view->wlr_xwayland_surface->demands_attention;
    kywc_view_set_demands_attention(&xwayland_view->view.base, demands_attention);
}

static void xwayland_view_handle_request_modal(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_modal);

    bool modal = xwayland_view->wlr_xwayland_surface->modal;
    kywc_view_set_modal(&xwayland_view->view.base, modal);
}

static void xwayland_view_handle_set_title(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_title);

    view_set_title(&xwayland_view->view, xwayland_view->wlr_xwayland_surface->title);
}

static void xwayland_view_handle_set_class(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class);

    view_set_app_id(&xwayland_view->view, xwayland_view->wlr_xwayland_surface->class);
}

static void xwayland_view_handle_set_parent(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_parent);
    struct wlr_xwayland_surface *parent = xwayland_view->wlr_xwayland_surface->parent;
    struct xwayland_view *parent_xwayland_view = parent ? parent->data : NULL;
    struct view *parent_view = parent_xwayland_view ? &parent_xwayland_view->view : NULL;

    view_set_parent(&xwayland_view->view, parent_view);
}

static void xwayland_view_handle_set_hints(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_hints);
    enum wlr_xwayland_icccm_input_model input_model =
        wlr_xwayland_icccm_input_model(xwayland_view->wlr_xwayland_surface);

    if (input_model == WLR_ICCCM_INPUT_MODEL_NONE) {
        xwayland_view->focusable = xwayland_view->activatable = false;
    } else {
        xwayland_view->focusable = xwayland_view->activatable = true;
    }
    view_update_capabilities(&xwayland_view->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE);
}

static void xwayland_view_handle_set_size_hints(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_size_hints);
    xwayland_view_update_geometry(xwayland_view);
}

static void xwayland_view_handle_set_functions(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_functions);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;

    bool new_minimizable =
        !(wlr_xwayland_surface->functions & WLR_XWAYLAND_SURFACE_FUNCTIONS_NO_MINIMIZE);
    bool new_maximizable =
        !(wlr_xwayland_surface->functions & WLR_XWAYLAND_SURFACE_FUNCTIONS_NO_MAXIMIZE);

    if (xwayland_view->minimizable != new_minimizable) {
        xwayland_view->minimizable = new_minimizable;
        view_update_capabilities(&xwayland_view->view,
                                 KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MINIMIZE_BUTTON);
    }
    if (xwayland_view->maximizable != new_maximizable) {
        xwayland_view->maximizable = new_maximizable;
        view_update_capabilities(&xwayland_view->view,
                                 KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON);
    }
}

static void xwayland_view_handle_set_decorations(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_decorations);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;

    if (xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_DOCK) ||
        xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_SPLASH) ||
        xwayland_surface_has_type(wlr_xwayland_surface, KDE_NET_WM_WINDOW_TYPE_OVERRIDE)) {
        view_set_decoration(&xwayland_view->view, KYWC_SSD_NONE);
        return;
    }

    /* disable ssd if the window has clip region */
    if (xwayland_view->view.surface) {
        struct ky_scene_buffer *buffer =
            ky_scene_buffer_try_from_surface(xwayland_view->view.surface);
        if (pixman_region32_not_empty(&buffer->node.clip_region)) {
            view_set_decoration(&xwayland_view->view, KYWC_SSD_NONE);
            return;
        }
    }

    enum kywc_ssd ssd = KYWC_SSD_ALL;
    if (wlr_xwayland_surface->decorations & WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER) {
        ssd &= ~KYWC_SSD_BORDER;
    }
    if (wlr_xwayland_surface->decorations & WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE) {
        ssd &= ~KYWC_SSD_TITLE;
    }
    /* don't add shadow if surface decoration is 0 */
    if (ssd == KYWC_SSD_RESIZE) {
        ssd = KYWC_SSD_NONE;
    }
    view_set_decoration(&xwayland_view->view, ssd);
}

static void xwayland_view_handle_view_above(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, view_above);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    if (wlr_xwayland_surface->above != kywc_view->kept_above) {
        wlr_xwayland_surface_set_above(wlr_xwayland_surface, kywc_view->kept_above);
    }
}

static void xwayland_view_handle_view_below(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, view_below);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    if (wlr_xwayland_surface->below != kywc_view->kept_below) {
        wlr_xwayland_surface_set_below(wlr_xwayland_surface, kywc_view->kept_below);
    }
}

static void xwayland_view_handle_view_sticky(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, view_sticky);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    if (wlr_xwayland_surface->sticky != kywc_view->sticky) {
        wlr_xwayland_surface_set_sticky(wlr_xwayland_surface, kywc_view->sticky);
    }
}

static void xwayland_view_handle_view_skip_taskbar(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, view_skip_taskbar);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    if (wlr_xwayland_surface->skip_taskbar != kywc_view->skip_taskbar) {
        wlr_xwayland_surface_set_skip_taskbar(wlr_xwayland_surface, kywc_view->skip_taskbar);
    }
}

static void xwayland_view_handle_view_demands_attention(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, view_demands_attention);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    if (wlr_xwayland_surface->demands_attention != kywc_view->demands_attention) {
        wlr_xwayland_surface_set_demands_attention(wlr_xwayland_surface,
                                                   kywc_view->demands_attention);
    }
}

static void xwayland_view_handle_ping_timeout(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, ping_timeout);
    view_show_hang_window(&xwayland_view->view);
}

static void xwayland_view_set_strut_partial(struct xwayland_view *xwayland_view)
{
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    xcb_ewmh_wm_strut_partial_t *strut = wlr_xwayland_surface->strut_partial;
    bool has_area =
        strut &&
        (strut->left_start_y != strut->left_end_y || strut->right_start_y != strut->right_end_y ||
         strut->top_start_x != strut->top_end_x || strut->bottom_start_x != strut->bottom_end_x);

    xwayland_view->movable = !has_area;
    view_update_capabilities(&xwayland_view->view, KYWC_VIEW_MOVABLE);
    xwayland_view->view.base.unconstrained = has_area;
    view_set_exclusive(&xwayland_view->view, has_area);
}

static void xwayland_view_handle_set_strut_partial(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, set_strut_partial);
    xwayland_view_set_strut_partial(xwayland_view);
}

static void xwayland_view_adjust_geometry(struct xwayland_view *xwayland_view, struct kywc_box *geo)
{
    struct kywc_view *kywc_view = &xwayland_view->view.base;
    struct kywc_output *kywc_output = kywc_output_at_point(geo->x, geo->y);

    if (kywc_view->fullscreen) {
        kywc_output_effective_geometry(kywc_output, geo);
        return;
    } else if (kywc_view->maximized) {
        view_get_tiled_geometry(&xwayland_view->view, geo, kywc_output, KYWC_TILE_ALL);
        return;
    } else if (kywc_view->tiled) {
        view_get_tiled_geometry(&xwayland_view->view, geo, kywc_output, kywc_view->tiled);
        return;
    }

    int max_width = kywc_view->max_width;
    int max_height = kywc_view->max_height;
    int width = 0, height = 0;
    output_layout_get_size(&width, &height);
    if (max_width <= 0 || max_width > width - kywc_view->margin.off_width) {
        max_width = width - kywc_view->margin.off_width;
    }
    if (max_height <= 0 || max_height > height - kywc_view->margin.off_height) {
        max_height = height - kywc_view->margin.off_height;
    }
    geo->width = CLAMP(geo->width, kywc_view->min_width, max_width);
    geo->height = CLAMP(geo->height, kywc_view->min_height, max_height);

    if (xwayland_view->view.exclusive_zone) {
        return;
    }

    struct output *output = output_from_kywc_output(kywc_output);
    if (kywc_view->has_initial_position) {
        int min_x = output->usable_area.x + kywc_view->margin.off_x;
        int min_y = output->usable_area.y + kywc_view->margin.off_y;
        int max_x = output->usable_area.x + output->usable_area.width;
        int max_y = output->usable_area.y + output->usable_area.height;
        geo->x = MAX(min_x, geo->x);
        geo->y = MAX(min_y, geo->y);

        if (geo->x > max_x) {
            geo->x = geo->width > output->usable_area.width ? min_x : max_x - geo->width;
        }
        if (geo->y > max_y) {
            geo->y = geo->height > output->usable_area.height ? min_y : max_y - geo->height;
        }
    } else {
        window_move_constraints(kywc_view, output, &geo->x, &geo->y, geo->width, geo->height);
    }
}

static void xwayland_view_apply_type(struct xwayland_view *xwayland_view)
{
    struct wlr_xwayland_surface *surface = xwayland_view->wlr_xwayland_surface;
    struct view_layer *layer = NULL;
    bool removed_from_workspace = false;

    if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DESKTOP)) {
        layer = view_manager_get_layer(LAYER_DESKTOP, false);
        xwayland_view->resizable = false;
        view_update_capabilities(&xwayland_view->view, KYWC_VIEW_RESIZABLE);
        xwayland_view->view.base.role = KYWC_VIEW_ROLE_DESKTOP;
        removed_from_workspace = true;
    } else if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DOCK)) {
        layer = view_manager_get_layer(LAYER_DOCK, false);
        xwayland_view->focusable = xwayland_view->activatable = xwayland_view->resizable = false;
        view_update_capabilities(&xwayland_view->view,
                                 KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE | KYWC_VIEW_RESIZABLE);
        xwayland_view->view.base.role = KYWC_VIEW_ROLE_PANEL;
        removed_from_workspace = true;
    } else if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_UTILITY) &&
               !xwayland_surface_has_input(surface, INPUT_MASK_KEYBOARD)) {
        xwayland_view->focusable = xwayland_view->activatable = false;
        view_update_capabilities(&xwayland_view->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE);
    }

    if (removed_from_workspace) {
        view_unset_workspace(&xwayland_view->view, layer);
    }

    xwayland_view->view.base.has_round_corner =
        xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_NORMAL) ||
        xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DIALOG);
}

void xwayland_view_set_skip_switcher(struct wlr_xwayland_surface *surface, bool skip_switcher)
{
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view) {
        return;
    }

    kywc_view_set_skip_switcher(&xwayland_view->view.base, skip_switcher);
}

static void xwayland_view_fixup_geometry(struct xwayland_view *xwayland_view)
{
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;
    struct kywc_box geo = kywc_view->geometry;

    if (wlr_xwayland_surface->x != 0 || wlr_xwayland_surface->y != 0) {
        geo.x = xwayland_unscale(wlr_xwayland_surface->x);
        geo.y = xwayland_unscale(wlr_xwayland_surface->y);
        kywc_view->has_initial_position = true;
    } else {
        /* apply the position in size_hints */
        xcb_size_hints_t *size_hints = wlr_xwayland_surface->size_hints;
        if (size_hints && size_hints->flags &
                              (XCB_ICCCM_SIZE_HINT_US_POSITION | XCB_ICCCM_SIZE_HINT_P_POSITION)) {
            geo.x = xwayland_unscale(size_hints->x);
            geo.y = xwayland_unscale(size_hints->y);
            kywc_view->has_initial_position = true;
        }
    }

    xwayland_view_adjust_geometry(xwayland_view, &geo);
    view_do_resize(&xwayland_view->view, &geo);
}

static bool wlr_xwayland_surface_is_window(struct wlr_xwayland_surface *surface)
{
    return surface && surface->surface && surface->class && *surface->class &&
           (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_NORMAL) ||
            xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DIALOG));
}

static bool xwayland_view_fixup_parent(struct xwayland_view *xwayland_view)
{
    if (wlr_xwayland_surface_is_window(xwayland_view->wlr_xwayland_surface->parent)) {
        return true;
    }

    struct view *parent = NULL;
    struct xwayland_view *xview;
    wl_list_for_each(xview, &xwayland_view->xwayland->surfaces, link) {
        if (!xview->view.base.mapped || xview == xwayland_view ||
            xview->wlr_xwayland_surface->pid != xwayland_view->wlr_xwayland_surface->pid) {
            continue;
        }
        if (wlr_xwayland_surface_is_window(xview->wlr_xwayland_surface)) {
            parent = &xview->view;
        }
    }
    view_set_parent(&xwayland_view->view, parent);
    return !!parent;
}

static void xwayland_view_handle_map(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, map);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;

    xwayland_view_update_geometry(xwayland_view);

    view_set_app_id(&xwayland_view->view, wlr_xwayland_surface->class);
    view_set_title(&xwayland_view->view, wlr_xwayland_surface->title);
    xwayland_view_handle_set_parent(&xwayland_view->set_parent, NULL);
    xwayland_view_handle_set_functions(&xwayland_view->set_functions, NULL);
    xwayland_view_handle_set_decorations(&xwayland_view->set_decorations, NULL);
    xwayland_view_handle_request_maximize(&xwayland_view->request_maximize, NULL);
    xwayland_view_handle_request_fullscreen(&xwayland_view->request_fullscreen, NULL);
    xwayland_view_handle_request_sticky(&xwayland_view->request_sticky, NULL);
    xwayland_view_handle_request_skip_taskbar(&xwayland_view->request_skip_taskbar, NULL);
    xwayland_view_handle_request_above(&xwayland_view->request_above, NULL);
    xwayland_view_handle_request_below(&xwayland_view->request_below, NULL);
    xwayland_view_handle_request_demands_attention(&xwayland_view->request_demands_attention, NULL);
    xwayland_view_handle_set_hints(&xwayland_view->set_hints, NULL);
    xwayland_apply_ukui_decoration(wlr_xwayland_surface->window_id, false);

    assert(wlr_xwayland_surface->surface == xwayland_view->view.surface);
    xwayland_view->commit.notify = xwayland_view_handle_commit;
    wl_signal_add(&wlr_xwayland_surface->surface->events.commit, &xwayland_view->commit);
    xwayland_view->set_strut_partial.notify = xwayland_view_handle_set_strut_partial;
    wl_signal_add(&wlr_xwayland_surface->events.set_strut_partial,
                  &xwayland_view->set_strut_partial);

    xwayland_view->request_move.notify = xwayland_view_handle_request_move;
    wl_signal_add(&wlr_xwayland_surface->events.request_move, &xwayland_view->request_move);
    xwayland_view->request_resize.notify = xwayland_view_handle_request_resize;
    wl_signal_add(&wlr_xwayland_surface->events.request_resize, &xwayland_view->request_resize);

    xwayland_view->request_maximize.notify = xwayland_view_handle_request_maximize;
    wl_signal_add(&wlr_xwayland_surface->events.request_maximize, &xwayland_view->request_maximize);
    xwayland_view->request_fullscreen.notify = xwayland_view_handle_request_fullscreen;
    wl_signal_add(&wlr_xwayland_surface->events.request_fullscreen,
                  &xwayland_view->request_fullscreen);
    xwayland_view->request_activate.notify = xwayland_view_handle_request_activate;
    wl_signal_add(&wlr_xwayland_surface->events.request_activate, &xwayland_view->request_activate);
    xwayland_view->request_sticky.notify = xwayland_view_handle_request_sticky;
    wl_signal_add(&wlr_xwayland_surface->events.request_sticky, &xwayland_view->request_sticky);
    xwayland_view->request_skip_taskbar.notify = xwayland_view_handle_request_skip_taskbar;
    wl_signal_add(&wlr_xwayland_surface->events.request_skip_taskbar,
                  &xwayland_view->request_skip_taskbar);
    xwayland_view->request_above.notify = xwayland_view_handle_request_above;
    wl_signal_add(&wlr_xwayland_surface->events.request_above, &xwayland_view->request_above);
    xwayland_view->request_below.notify = xwayland_view_handle_request_below;
    wl_signal_add(&wlr_xwayland_surface->events.request_below, &xwayland_view->request_below);
    xwayland_view->request_demands_attention.notify =
        xwayland_view_handle_request_demands_attention;
    wl_signal_add(&wlr_xwayland_surface->events.request_demands_attention,
                  &xwayland_view->request_demands_attention);
    xwayland_view->request_modal.notify = xwayland_view_handle_request_modal;
    wl_signal_add(&wlr_xwayland_surface->events.request_modal, &xwayland_view->request_modal);

    xwayland_view->set_title.notify = xwayland_view_handle_set_title;
    wl_signal_add(&wlr_xwayland_surface->events.set_title, &xwayland_view->set_title);
    xwayland_view->set_class.notify = xwayland_view_handle_set_class;
    wl_signal_add(&wlr_xwayland_surface->events.set_class, &xwayland_view->set_class);
    xwayland_view->set_parent.notify = xwayland_view_handle_set_parent;
    wl_signal_add(&wlr_xwayland_surface->events.set_parent, &xwayland_view->set_parent);
    xwayland_view->ping_timeout.notify = xwayland_view_handle_ping_timeout;
    wl_signal_add(&wlr_xwayland_surface->events.ping_timeout, &xwayland_view->ping_timeout);

    xwayland_view->set_hints.notify = xwayland_view_handle_set_hints;
    wl_signal_add(&wlr_xwayland_surface->events.set_hints, &xwayland_view->set_hints);
    xwayland_view->set_size_hints.notify = xwayland_view_handle_set_size_hints;
    wl_signal_add(&wlr_xwayland_surface->events.set_size_hints, &xwayland_view->set_size_hints);
    xwayland_view->set_functions.notify = xwayland_view_handle_set_functions;
    wl_signal_add(&wlr_xwayland_surface->events.set_functions, &xwayland_view->set_functions);
    xwayland_view->set_decorations.notify = xwayland_view_handle_set_decorations;
    wl_signal_add(&wlr_xwayland_surface->events.set_decorations, &xwayland_view->set_decorations);
    xwayland_view->view_above.notify = xwayland_view_handle_view_above;
    wl_signal_add(&xwayland_view->view.base.events.above, &xwayland_view->view_above);
    xwayland_view->view_below.notify = xwayland_view_handle_view_below;
    wl_signal_add(&xwayland_view->view.base.events.below, &xwayland_view->view_below);
    xwayland_view->view_sticky.notify = xwayland_view_handle_view_sticky;
    wl_signal_add(&xwayland_view->view.base.events.sticky, &xwayland_view->view_sticky);
    xwayland_view->view_skip_taskbar.notify = xwayland_view_handle_view_skip_taskbar;
    wl_signal_add(&xwayland_view->view.base.events.skip_taskbar, &xwayland_view->view_skip_taskbar);
    xwayland_view->view_demands_attention.notify = xwayland_view_handle_view_demands_attention;
    wl_signal_add(&xwayland_view->view.base.events.demands_attention,
                  &xwayland_view->view_demands_attention);

    xwayland_view_apply_type(xwayland_view);

    /* we should stack above the new window always */
    if (!KYWC_VIEW_IS_ACTIVATABLE(&xwayland_view->view.base)) {
        xwayland_restack_view(xwayland_view);
    }

    xwayland_view_fixup_geometry(xwayland_view);

    xwayland_view->view.pid = wlr_xwayland_surface->pid;

    bool modal = wlr_xwayland_surface->modal;
    if (modal && wlr_xwayland_surface->parent) {
        modal = xwayland_view_fixup_parent(xwayland_view);
    }
    kywc_view_set_modal(&xwayland_view->view.base, modal);

    view_map(&xwayland_view->view);

    xwayland_view_set_strut_partial(xwayland_view);
    xwayland_fixup_pointer_position(wlr_xwayland_surface->surface);
}

static void xwayland_view_handle_unmap(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap);

    wl_list_remove(&xwayland_view->commit.link);
    wl_list_remove(&xwayland_view->set_strut_partial.link);
    wl_list_remove(&xwayland_view->request_move.link);
    wl_list_remove(&xwayland_view->request_resize.link);
    wl_list_remove(&xwayland_view->request_maximize.link);
    wl_list_remove(&xwayland_view->request_fullscreen.link);
    wl_list_remove(&xwayland_view->request_activate.link);
    wl_list_remove(&xwayland_view->request_sticky.link);
    wl_list_remove(&xwayland_view->request_skip_taskbar.link);
    wl_list_remove(&xwayland_view->request_above.link);
    wl_list_remove(&xwayland_view->request_below.link);
    wl_list_remove(&xwayland_view->request_demands_attention.link);
    wl_list_remove(&xwayland_view->request_modal.link);
    wl_list_remove(&xwayland_view->set_title.link);
    wl_list_remove(&xwayland_view->set_class.link);
    wl_list_remove(&xwayland_view->set_parent.link);
    wl_list_remove(&xwayland_view->set_hints.link);
    wl_list_remove(&xwayland_view->set_size_hints.link);
    wl_list_remove(&xwayland_view->set_functions.link);
    wl_list_remove(&xwayland_view->set_decorations.link);
    wl_list_remove(&xwayland_view->view_above.link);
    wl_list_remove(&xwayland_view->view_below.link);
    wl_list_remove(&xwayland_view->view_sticky.link);
    wl_list_remove(&xwayland_view->view_skip_taskbar.link);
    wl_list_remove(&xwayland_view->view_demands_attention.link);
    wl_list_remove(&xwayland_view->ping_timeout.link);

    if (xwayland_view->xwayland->activated_surface == xwayland_view->wlr_xwayland_surface) {
        xwayland_view->xwayland->activated_surface = NULL;
    }

    /* surface_tree is destroyed by scene subsurface */
    view_unmap(&xwayland_view->view);
}

static void xwayland_view_handle_surface_tree_destroy(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, surface_tree_destroy);
    wl_list_remove(&xwayland_view->surface_tree_destroy.link);
    xwayland_view->view.surface_tree = NULL;
}

static void xwayland_view_handle_client_commit(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, client_commit);
    if (xwayland_view->xwayland->scale == 1.0) {
        return;
    }

    struct wlr_surface_state *pending = &xwayland_view->wlr_xwayland_surface->surface->pending;
    pending->width = xwayland_unscale(pending->width);
    pending->height = xwayland_unscale(pending->height);

    float scale = 1.0 / xwayland_view->xwayland->scale;
    if (pending->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) {
        wlr_region_scale(&pending->surface_damage, &pending->surface_damage, scale);
    }
    if (pending->committed & WLR_SURFACE_STATE_OPAQUE_REGION) {
        wlr_region_scale(&pending->opaque, &pending->opaque, scale);
    }
    if (pending->committed & WLR_SURFACE_STATE_INPUT_REGION) {
        wlr_region_scale(&pending->input, &pending->input, scale);
    }
    if (pending->committed & WLR_SURFACE_STATE_OFFSET) {
        pending->dx = xwayland_unscale(pending->dx);
        pending->dy = xwayland_unscale(pending->dy);
    }
}

static void xwayland_view_handle_associate(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;

    xwayland_view->view.surface = wlr_xwayland_surface->surface;
    wlr_xwayland_surface->surface->data = xwayland_view;

    xwayland_view->unmap.notify = xwayland_view_handle_unmap;
    wl_signal_add(&wlr_xwayland_surface->surface->events.unmap, &xwayland_view->unmap);

    /* create scene tree here as we get surface here */
    xwayland_view->view.surface_tree =
        ky_scene_subsurface_tree_create(xwayland_view->view.tree, wlr_xwayland_surface->surface);
    /* event node will be destroyed when surface_node destroy */
    input_event_node_create(&xwayland_view->view.surface_tree->node, &xwayland_view_event_node_impl,
                            xwayland_view_get_root, xwayland_view_get_toplevel, xwayland_view);

    xwayland_surface_shape_select_input(wlr_xwayland_surface, true);
    xwayland_surface_apply_shape_region(wlr_xwayland_surface);

    // read all surface properties
    xwayland_read_wm_state(wlr_xwayland_surface->window_id);
    xwayland_read_wm_icon(wlr_xwayland_surface->window_id);
    xwayland_apply_wm_window_opacity(wlr_xwayland_surface->window_id, false);
    xwayland_apply_blur_region(wlr_xwayland_surface->window_id, false);
    xwayland_apply_opaque_region(wlr_xwayland_surface->window_id, false);

    xwayland_view->sync.counter = xwayland_get_sync_counter(wlr_xwayland_surface->window_id);
    xwayland_view->sync.alarm =
        xwayland_create_sync_alarm(wlr_xwayland_surface->window_id, xwayland_view->sync.counter);

    xwayland_view->client_commit.notify = xwayland_view_handle_client_commit;
    wl_signal_add(&wlr_xwayland_surface->surface->events.client_commit,
                  &xwayland_view->client_commit);
    xwayland_view->map.notify = xwayland_view_handle_map;
    wl_signal_add(&wlr_xwayland_surface->surface->events.map, &xwayland_view->map);
    xwayland_view->surface_tree_destroy.notify = xwayland_view_handle_surface_tree_destroy;
    wl_signal_add(&xwayland_view->view.surface_tree->node.events.destroy,
                  &xwayland_view->surface_tree_destroy);
}

static void xwayland_view_handle_dissociate(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate);

    xwayland_view->wlr_xwayland_surface->surface->data = NULL;
    xwayland_view->view.surface = NULL;
    if (xwayland_view->view.surface_tree) {
        ky_scene_node_destroy(&xwayland_view->view.surface_tree->node);
    }

    if (xwayland_view->sync.alarm) {
        xcb_sync_destroy_alarm(xwayland_view->xwayland->xcb_conn, xwayland_view->sync.alarm);
        xwayland_set_allow_commits(xwayland_view->wlr_xwayland_surface->window_id, true);
        xwayland_view->sync.blocked = false;
    }

    wl_list_remove(&xwayland_view->client_commit.link);
    wl_list_remove(&xwayland_view->map.link);
    wl_list_remove(&xwayland_view->unmap.link);
}

static void xwayland_view_icon_destroy(struct net_wm_icon *icon)
{
    wl_list_remove(&icon->link);
    wlr_buffer_drop(icon->buffer);
    free(icon->data);
    free(icon);
}

static void xwayland_view_handle_destroy(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy);

    wl_list_remove(&xwayland_view->link);
    wl_list_remove(&xwayland_view->destroy.link);
    wl_list_remove(&xwayland_view->associate.link);
    wl_list_remove(&xwayland_view->dissociate.link);
    wl_list_remove(&xwayland_view->request_configure.link);
    wl_list_remove(&xwayland_view->request_minimize.link);
    wl_list_remove(&xwayland_view->set_override_redirect.link);
    wl_list_remove(&xwayland_view->view_update_capabilities.link);

    xwayland_view_clear_wm_icon(xwayland_view->wlr_xwayland_surface);

    view_destroy(&xwayland_view->view);
}

static void xwayland_view_handle_set_override_redirect(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, set_override_redirect);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct xwayland_server *xwayland = xwayland_view->xwayland;

    if (wlr_xwayland_surface->surface) {
        if (wlr_xwayland_surface->surface->mapped) {
            xwayland_view_handle_unmap(&xwayland_view->unmap, NULL);
        }
        xwayland_view_handle_dissociate(&xwayland_view->dissociate, NULL);
    }
    xwayland_view_handle_destroy(&xwayland_view->destroy, NULL);
    wlr_xwayland_surface->data = NULL;

    xwayland_unmanaged_create(xwayland, wlr_xwayland_surface);
}

static void xwayland_view_handle_update_capabilities(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, view_update_capabilities);
    struct view_update_capabilities_event *event = data;

    if (event->mask & KYWC_VIEW_MINIMIZABLE) {
        if (!xwayland_view->minimizable) {
            event->state &= ~KYWC_VIEW_MINIMIZABLE;
        }
    }
    if (event->mask & KYWC_VIEW_MAXIMIZABLE) {
        if (!xwayland_view->maximizable) {
            event->state &= ~KYWC_VIEW_MAXIMIZABLE;
        }
    }
    if (event->mask & KYWC_VIEW_FOCUSABLE) {
        if (!xwayland_view->focusable) {
            event->state &= ~KYWC_VIEW_FOCUSABLE;
        }
    }
    if (event->mask & KYWC_VIEW_ACTIVATABLE) {
        if (!xwayland_view->activatable) {
            event->state &= ~KYWC_VIEW_ACTIVATABLE;
        }
    }
    if (event->mask & KYWC_VIEW_MOVABLE) {
        if (!xwayland_view->movable) {
            event->state &= ~KYWC_VIEW_MOVABLE;
        }
    }
    if (event->mask & KYWC_VIEW_RESIZABLE) {
        if (!xwayland_view->resizable) {
            event->state &= ~KYWC_VIEW_RESIZABLE;
        }
    }
    if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) {
        if (!xwayland_view->minimizable) {
            event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON;
        }
    }
    if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) {
        if (!xwayland_view->maximizable) {
            event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON;
        }
    }
}

static void xwayland_view_handle_request_configure(struct wl_listener *listener, void *data)
{
    struct xwayland_view *xwayland_view =
        wl_container_of(listener, xwayland_view, request_configure);
    struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface;
    struct kywc_view *kywc_view = &xwayland_view->view.base;

    /* skip configure when moving or resizing by left or top edges */
    if (kywc_view->mapped &&
        (xwayland_view->view.interactive_moving ||
         (xwayland_view->view.current_resize_edges & (KYWC_EDGE_LEFT | KYWC_EDGE_TOP)))) {
        wlr_xwayland_surface_configure(wlr_xwayland_surface, wlr_xwayland_surface->x,
                                       wlr_xwayland_surface->y, wlr_xwayland_surface->width,
                                       wlr_xwayland_surface->height);
        return;
    }

    struct wlr_xwayland_surface_configure_event *event = data;
    struct kywc_box geo = { xwayland_unscale(event->x), xwayland_unscale(event->y),
                            xwayland_unscale(event->width), xwayland_unscale(event->height) };

    if (!kywc_view->mapped) {
        wlr_xwayland_surface_configure(wlr_xwayland_surface, event->x, event->y, event->width,
                                       event->height);
    } else {
        xwayland_view_adjust_geometry(xwayland_view, &geo);
    }

    view_do_resize(&xwayland_view->view, &geo);
}

void xwayland_view_create(struct xwayland_server *xwayland,
                          struct wlr_xwayland_surface *wlr_xwayland_surface)
{
    struct xwayland_view *xwayland_view = calloc(1, sizeof(*xwayland_view));
    if (!xwayland_view) {
        return;
    }

    xwayland_view->xwayland = xwayland;
    wl_list_insert(&xwayland->surfaces, &xwayland_view->link);
    view_init(&xwayland_view->view, &xwl_surface_impl, xwayland_view);

    xwayland_view->wlr_xwayland_surface = wlr_xwayland_surface;
    wlr_xwayland_surface->data = xwayland_view;

    xwayland_view->associate.notify = xwayland_view_handle_associate;
    wl_signal_add(&wlr_xwayland_surface->events.associate, &xwayland_view->associate);
    xwayland_view->dissociate.notify = xwayland_view_handle_dissociate;
    wl_signal_add(&wlr_xwayland_surface->events.dissociate, &xwayland_view->dissociate);
    xwayland_view->destroy.notify = xwayland_view_handle_destroy;
    wl_signal_add(&wlr_xwayland_surface->events.destroy, &xwayland_view->destroy);

    xwayland_view->request_configure.notify = xwayland_view_handle_request_configure;
    wl_signal_add(&wlr_xwayland_surface->events.request_configure,
                  &xwayland_view->request_configure);
    xwayland_view->request_minimize.notify = xwayland_view_handle_request_minimize;
    wl_signal_add(&wlr_xwayland_surface->events.request_minimize, &xwayland_view->request_minimize);
    xwayland_view->set_override_redirect.notify = xwayland_view_handle_set_override_redirect;
    wl_signal_add(&wlr_xwayland_surface->events.set_override_redirect,
                  &xwayland_view->set_override_redirect);

    wl_list_init(&xwayland_view->client_commit.link);
    wl_list_init(&xwayland_view->map.link);
    wl_list_init(&xwayland_view->unmap.link);
    wl_list_init(&xwayland_view->net_wm_icons);

    xwayland_view->minimizable = xwayland_view->maximizable = xwayland_view->focusable =
        xwayland_view->activatable = xwayland_view->movable = xwayland_view->resizable = true;
    xwayland_view->view_update_capabilities.notify = xwayland_view_handle_update_capabilities;
    view_add_update_capabilities_listener(&xwayland_view->view,
                                          &xwayland_view->view_update_capabilities);

    if (wlr_xwayland_surface->surface) {
        xwayland_view_handle_associate(&xwayland_view->associate, NULL);
        if (wlr_xwayland_surface->surface->mapped) {
            xwayland_view_handle_map(&xwayland_view->map, NULL);
        }
    }
}

struct wlr_xwayland_surface *xwayland_view_look_surface(struct xwayland_server *xwayland,
                                                        xcb_window_t window_id)
{
    struct xwayland_view *xwayland_view;
    wl_list_for_each(xwayland_view, &xwayland->surfaces, link) {
        if (xwayland_view->wlr_xwayland_surface->window_id == window_id) {
            return xwayland_view->wlr_xwayland_surface;
        }
    }
    return NULL;
}

void xwayland_view_clear_wm_icon(struct wlr_xwayland_surface *surface)
{
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view) {
        return;
    }

    struct net_wm_icon *icon, *tmp;
    wl_list_for_each_safe(icon, tmp, &xwayland_view->net_wm_icons, link) {
        xwayland_view_icon_destroy(icon);
    }
}

void xwayland_view_add_new_wm_icon(struct wlr_xwayland_surface *surface, uint32_t width,
                                   uint32_t height, uint32_t size, uint32_t *data)
{
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view) {
        return;
    }

    struct net_wm_icon *old_icon, *tmp;
    wl_list_for_each_safe(old_icon, tmp, &xwayland_view->net_wm_icons, link) {
        if (old_icon->width == width && old_icon->height == height) {
            xwayland_view_icon_destroy(old_icon);
        }
    }

    struct net_wm_icon *icon = calloc(1, sizeof(*icon));
    if (!icon) {
        return;
    }
    wl_list_insert(&xwayland_view->net_wm_icons, &icon->link);
    icon->width = width;
    icon->height = height;
    icon->data = (unsigned char *)malloc(size);
    memcpy(icon->data, (unsigned char *)data, size);
}

void xwayland_view_update_icon(struct wlr_xwayland_surface *surface)
{
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view) {
        return;
    }

    view_set_icon(&xwayland_view->view, true);
}

bool xwayland_view_set_opacity(struct xwayland_server *xwayland, xcb_window_t window_id,
                               float opacity)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }

    struct xwayland_view *xwayland_view = surface->data;
    if (xwayland_view->view.surface) {
        struct ky_scene_buffer *buffer =
            ky_scene_buffer_try_from_surface(xwayland_view->view.surface);
        ky_scene_buffer_set_opacity(buffer, opacity);
    }

    return true;
}

bool xwayland_view_set_shape_region(struct xwayland_server *xwayland, xcb_window_t window_id,
                                    xcb_shape_sk_t kind, const pixman_region32_t *region)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view->view.surface) {
        return true;
    }

    struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface);
    if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_CLIP) {
        ky_scene_node_set_clip_region(&buffer->node, region);
    }
    if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_INPUT) {
        ky_scene_node_set_input_region(&buffer->node, region);
        /* empty input region means no input support */
        bool need_bypassed = kind == XCB_SHAPE_SK_INPUT && !pixman_region32_not_empty(region);
        ky_scene_node_set_input_bypassed(&buffer->node, need_bypassed);
    }

    /* sync the decoration if clip region is changed */
    xwayland_view_handle_set_decorations(&xwayland_view->set_decorations, NULL);

    return true;
}

bool xwayland_view_set_blur_region(struct xwayland_server *xwayland, xcb_window_t window_id,
                                   const pixman_region32_t *region)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view->view.surface) {
        return true;
    }

    struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface);
    if (!buffer) {
        return true;
    }
    ky_scene_node_set_blur_region(&buffer->node, region);

    return true;
}

bool xwayland_view_set_opaque_region(struct xwayland_server *xwayland, xcb_window_t window_id,
                                     const pixman_region32_t *region)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view->view.surface) {
        return true;
    }

    struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface);
    ky_scene_buffer_set_opaque_region(buffer, region);

    return true;
}

bool xwayland_view_set_no_title(struct xwayland_server *xwayland, xcb_window_t window_id, bool set)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }

    struct xwayland_view *xwayland_view = surface->data;
    enum kywc_ssd ssd = xwayland_view->view.base.ssd;
    if (set) {
        ssd &= ~KYWC_SSD_TITLE;
    } else {
        ssd |= KYWC_SSD_TITLE;
    }
    view_set_decoration(&xwayland_view->view, ssd);

    return true;
}

void xwayland_view_move_resize(struct wlr_xwayland_surface *surface, struct kywc_box *box)
{
    struct xwayland_view *xwayland_view = surface->data;
    struct kywc_box geo = { xwayland_unscale(box->x), xwayland_unscale(box->y),
                            xwayland_unscale(box->width), xwayland_unscale(box->height) };
    xwayland_view_adjust_geometry(xwayland_view, &geo);
    view_do_resize(&xwayland_view->view, &geo);
}

bool xwayland_check_view(struct view *view)
{
    return view->impl == &xwl_surface_impl;
}

bool xwayland_view_reset_sync_counter(struct xwayland_server *xwayland, xcb_window_t window_id)
{
    struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id);
    if (!surface) {
        return false;
    }
    struct xwayland_view *xwayland_view = surface->data;
    if (!xwayland_view->view.surface) {
        return true;
    }

    xwayland_view->sync.value = (xcb_sync_int64_t){ 0, 0 };
    return true;
}

bool xwayland_view_ack_sync(struct xwayland_server *xwayland, xcb_sync_alarm_t alarm,
                            xcb_sync_int64_t value)
{
    struct xwayland_view *xwayland_view;
    wl_list_for_each(xwayland_view, &xwayland->surfaces, link) {
        if (xwayland_view->sync.alarm != alarm) {
            continue;
        }
        if (xwayland_view->sync.value.lo == value.lo && xwayland_view->sync.value.hi == value.hi &&
            xwayland_view->sync.blocked) {
            xwayland_set_allow_commits(xwayland_view->wlr_xwayland_surface->window_id, true);
            xwayland_view->sync.blocked = false;
        }
        return true;
    }
    return false;
}

void xwayland_view_configure_all(struct xwayland_server *xwayland, struct kywc_output *output)
{
    struct xwayland_view *xwayland_view;
    wl_list_for_each(xwayland_view, &xwayland->surfaces, link) {
        struct view *view = &xwayland_view->view;
        if (!view->base.mapped) {
            continue;
        }
        if (view->output == output) {
            continue;
        }

        int lx = view->base.geometry.x - view->base.margin.off_x;
        int ly = view->base.geometry.y - view->base.margin.off_y;
        if (!kywc_output_contains_point(view->output, lx, ly)) {
            continue;
        }

        view_move_to_output(view, NULL, NULL, view->output);
    }
}
