#define _POSIX_C_SOURCE    200112L

/** Our implementation details */
#include "tinywl.h"


#define WLR_USE_UNSTABLE

#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include <linux/input-event-codes.h>
#include <wlr/backend/session.h>

static void focus_view( struct tinywl_view *view, struct wlr_surface *surface ) {
    /* Note: this function only deals with keyboard focus. */
    if ( view == NULL ) {
        return;
    }

    struct tinywl_server *server       = view->server;
    struct wlr_seat      *seat         = server->seat;
    struct wlr_surface   *prev_surface = seat->keyboard_state.focused_surface;

    if ( prev_surface == surface ) {
        /* Don't re-focus an already focused surface. */
        return;
    }

    if ( prev_surface ) {
        /*
         * Deactivate the previously focused surface. This lets the client know
         * it no longer has focus and the client will repaint accordingly, e.g.
         * stop displaying a caret.
         */
        struct wlr_xdg_toplevel *previous = wlr_xdg_toplevel_try_from_wlr_surface( prev_surface );

        if ( previous != NULL ) {
            wlr_xdg_toplevel_set_activated( previous, false );
        }
    }

    struct wlr_keyboard *keyboard = wlr_seat_get_keyboard( seat );

    /* Move the view to the front */
    wlr_scene_node_raise_to_top( &view->scene_tree->node );
    wl_list_remove( &view->link );
    wl_list_insert( &server->views, &view->link );
    /* Activate the new surface */
    wlr_xdg_toplevel_set_activated( view->xdg_toplevel, true );

    /*
     * Tell the seat to have the keyboard enter this surface. wlroots will keep
     * track of this and automatically send key events to the appropriate
     * clients without additional work on your part.
     */
    if ( keyboard != NULL ) {
        wlr_seat_keyboard_notify_enter( seat, view->xdg_toplevel->base->surface,
                                        keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers );
    }
}


static void keyboard_handle_modifiers( struct wl_listener *listener, void * ) {
    /** This event is raised when a modifier key, such as shift or alt, is
     * pressed. We simply communicate this to the client. */
    struct tinywl_keyboard *keyboard =
        wl_container_of( listener, keyboard, modifiers );

    /*
     * A seat can only have one keyboard, but this is a limitation of the
     * Wayland protocol - not wlroots. We assign all connected keyboards to the
     * same seat. You can swap out the underlying wlr_keyboard like this and
     * wlr_seat handles this transparently.
     */
    wlr_seat_set_keyboard( keyboard->server->seat, keyboard->wlr_keyboard );
    /** Send modifiers to the client. */
    wlr_seat_keyboard_notify_modifiers( keyboard->server->seat, &keyboard->wlr_keyboard->modifiers );
}


static bool checkKeyBinding( uint32_t key, uint32_t mods ) {
    if ( mods ^ (WLR_MODIFIER_ALT | WLR_MODIFIER_CTRL) ) {
        return false;
    }

    if ( key == KEY_Q ) {
        haltWlEventLoop();
        return true;
    }

    else if ( (key >= KEY_F1) || (key <= KEY_F12) ) {
        int target_vt = key - KEY_F1 + 1;
        wlr_session_change_vt( server->session, target_vt );

        return true;
    }

    return false;
}


static void keyboard_handle_key( struct wl_listener *listener, void *data ) {
    /** This event is raised when a key is pressed or released. */
    struct tinywl_keyboard        *keyboard = wl_container_of( listener, keyboard, key );
    struct tinywl_server          *server   = keyboard->server;
    struct wlr_keyboard_key_event *event    = data;
    struct wlr_seat               *seat     = server->seat;

    uint32_t keycode = event->keycode;

    bool handled = false;

    /** If some key was pressed: check if we're trying to change the tty */
    if ( event->state == WL_KEYBOARD_KEY_STATE_PRESSED ) {
        uint32_t modifiers = wlr_keyboard_get_modifiers( keyboard->wlr_keyboard );

        if ( server->session ) {
            handled = checkKeyBinding( keycode, modifiers );
        }
    }

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


static void keyboard_handle_destroy( struct wl_listener *listener, void * ) {
    /* This event is raised by the keyboard base wlr_input_device to signal
     * the destruction of the wlr_keyboard. It will no longer receive events
     * and should be destroyed.
     */
    struct tinywl_keyboard *keyboard = wl_container_of( listener, keyboard, destroy );

    wl_list_remove( &keyboard->modifiers.link );
    wl_list_remove( &keyboard->key.link );
    wl_list_remove( &keyboard->destroy.link );
    wl_list_remove( &keyboard->link );
    free( keyboard );
}


static void server_new_keyboard( struct tinywl_server *server, struct wlr_input_device *device ) {
    struct wlr_keyboard    *wlr_keyboard = wlr_keyboard_from_input_device( device );
    struct tinywl_keyboard *keyboard     = calloc( 1, sizeof(struct tinywl_keyboard) );

    keyboard->server       = server;
    keyboard->wlr_keyboard = wlr_keyboard;

    /** We need to prepare an XKB keymap and assign it to the keyboard. This
     * assumes the defaults (e.g. layout = "us"). */
    struct xkb_context *context = xkb_context_new( XKB_CONTEXT_NO_FLAGS );
    struct xkb_keymap  *keymap  = xkb_keymap_new_from_names( context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS );

    wlr_keyboard_set_keymap( wlr_keyboard, keymap );
    xkb_keymap_unref( keymap );
    xkb_context_unref( context );
    wlr_keyboard_set_repeat_info( wlr_keyboard, 25, 600 );

    /** Here we set up listeners for keyboard events. */
    keyboard->modifiers.notify = keyboard_handle_modifiers;
    wl_signal_add( &wlr_keyboard->events.modifiers, &keyboard->modifiers );
    keyboard->key.notify = keyboard_handle_key;
    wl_signal_add( &wlr_keyboard->events.key,       &keyboard->key );
    keyboard->destroy.notify = keyboard_handle_destroy;
    wl_signal_add( &device->events.destroy,         &keyboard->destroy );

    wlr_seat_set_keyboard( server->seat, keyboard->wlr_keyboard );

    /** And add the keyboard to our list of keyboards */
    wl_list_insert( &server->keyboards, &keyboard->link );
}


static void server_new_pointer( struct tinywl_server *server, struct wlr_input_device *device ) {
    /** We don't do anything special with pointers. All of our pointer handling
     * is proxied through wlr_cursor. On another compositor, you might take this
     * opportunity to do libinput configuration on the device to set
     * acceleration, etc. */
    wlr_cursor_attach_input_device( server->cursor, device );
}


static void server_new_input( struct wl_listener *listener, void *data ) {
    /** This event is raised by the backend when a new input device becomes available. */
    struct tinywl_server    *server = wl_container_of( listener, server, new_input );
    struct wlr_input_device *device = data;

    switch ( device->type ) {
        case WLR_INPUT_DEVICE_KEYBOARD: {
            server_new_keyboard( server, device );
            break;
        }

        case WLR_INPUT_DEVICE_POINTER: {
            server_new_pointer( server, device );
            break;
        }

        default: {
            break;
        }
    }

    /** We need to let the wlr_seat know what our capabilities are, which is
    * communiciated to the client. In TinyWL we always have a cursor, even if
    * there are no pointer devices, so we always include that capability. */
    uint32_t caps = WL_SEAT_CAPABILITY_POINTER;

    if ( !wl_list_empty( &server->keyboards ) ) {
        caps |= WL_SEAT_CAPABILITY_KEYBOARD;
    }

    wlr_seat_set_capabilities( server->seat, caps );
}


static void seat_request_cursor( struct wl_listener *listener, void *data ) {
    struct tinywl_server *server = wl_container_of(
        listener, server, request_cursor );
    /** This event is raised by the seat when a client provides a cursor image */
    struct wlr_seat_pointer_request_set_cursor_event *event = data;
    struct wlr_seat_client *focused_client =
        server->seat->pointer_state.focused_client;

    /** This can be sent by any client, so we check to make sure this one is
     * actually has pointer focus first. */
    if ( focused_client == event->seat_client ) {
        /** Once we've vetted the client, we can tell the cursor to use the
         * provided surface as the cursor image. It will set the hardware cursor
         * on the output that it's currently on and continue to do so as the
         * cursor moves between outputs. */
        wlr_cursor_set_surface( server->cursor, event->surface,
                                event->hotspot_x, event->hotspot_y );
    }
}


static void seat_request_set_selection( struct wl_listener *listener, void *data ) {
    /** This event is raised by the seat when a client wants to set the selection,
     * usually when the user copies something. wlroots allows compositors to
     * ignore such requests if they so choose, but in tinywl we always honor
     */
    struct tinywl_server *server = wl_container_of(
        listener, server, request_set_selection );
    struct wlr_seat_request_set_selection_event *event = data;

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


static struct tinywl_view *desktop_view_at( struct tinywl_server *server, double lx, double ly,
                                            struct wlr_surface **surface, double *sx, double *sy ) {
    /** This returns the topmost node in the scene at the given layout coords.
     * we only care about surface nodes as we are specifically looking for a
     * surface in the surface tree of a tinywl_view. */
    struct wlr_scene_node *node = wlr_scene_node_at(
        &server->scene->tree.node, lx, ly, sx, sy );

    if ( (node == NULL) || (node->type != WLR_SCENE_NODE_BUFFER) ) {
        return NULL;
    }

    struct wlr_scene_buffer  *scene_buffer  = wlr_scene_buffer_from_node( node );
    struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer( scene_buffer );

    if ( !scene_surface ) {
        return NULL;
    }

    *surface = scene_surface->surface;

    /* Find the node corresponding to the tinywl_view at the root of this
     * surface tree, it is the only one for which we set the data field. */
    struct wlr_scene_tree *tree = node->parent;

    while ( tree != NULL && tree->node.data == NULL ) {
        tree = tree->node.parent;
    }
    return tree->node.data;
}


static void reset_cursor_mode( struct tinywl_server *server ) {
    /* Reset the cursor mode to passthrough. */
    server->cursor_mode  = TINYWL_CURSOR_PASSTHROUGH;
    server->grabbed_view = NULL;
}


static void process_cursor_move( struct tinywl_server *server, uint32_t ) {
    /** Move the grabbed view to the new position. */
    struct tinywl_view *view = server->grabbed_view;

    view->x = server->cursor->x - server->grab_x;
    view->y = server->cursor->y - server->grab_y;
    wlr_scene_node_set_position( &view->scene_tree->node, view->x, view->y );
}


static void process_cursor_resize( struct tinywl_server *server, uint32_t ) {
    /*
     * Resizing the grabbed view can be a little bit complicated, because we
     * could be resizing from any corner or edge. This not only resizes the view
     * on one or two axes, but can also move the view if you resize from the top
     * or left edges (or top-left corner).
     *
     * Note that I took some shortcuts here. In a more fleshed-out compositor,
     * you'd wait for the client to prepare a buffer at the new size, then
     * commit any movement that was prepared.
     */
    struct tinywl_view *view      = server->grabbed_view;
    double             border_x   = server->cursor->x - server->grab_x;
    double             border_y   = server->cursor->y - server->grab_y;
    int                new_left   = server->grab_geobox.x;
    int                new_right  = server->grab_geobox.x + server->grab_geobox.width;
    int                new_top    = server->grab_geobox.y;
    int                new_bottom = server->grab_geobox.y + server->grab_geobox.height;

    if ( server->resize_edges & WLR_EDGE_TOP ) {
        new_top = border_y;

        if ( new_top >= new_bottom ) {
            new_top = new_bottom - 1;
        }
    }
    else if ( server->resize_edges & WLR_EDGE_BOTTOM ) {
        new_bottom = border_y;

        if ( new_bottom <= new_top ) {
            new_bottom = new_top + 1;
        }
    }

    if ( server->resize_edges & WLR_EDGE_LEFT ) {
        new_left = border_x;

        if ( new_left >= new_right ) {
            new_left = new_right - 1;
        }
    }
    else if ( server->resize_edges & WLR_EDGE_RIGHT ) {
        new_right = border_x;

        if ( new_right <= new_left ) {
            new_right = new_left + 1;
        }
    }

    struct wlr_box geo_box;

    wlr_xdg_surface_get_geometry( view->xdg_toplevel->base, &geo_box );
    view->x = new_left - geo_box.x;
    view->y = new_top - geo_box.y;
    wlr_scene_node_set_position( &view->scene_tree->node, view->x, view->y );

    int new_width  = new_right - new_left;
    int new_height = new_bottom - new_top;

    wlr_xdg_toplevel_set_size( view->xdg_toplevel, new_width, new_height );
}


static void process_cursor_motion( struct tinywl_server *server, uint32_t time ) {
    /** If the mode is non-passthrough, delegate to those functions. */
    if ( server->cursor_mode == TINYWL_CURSOR_MOVE ) {
        process_cursor_move( server, time );
        return;
    }
    else if ( server->cursor_mode == TINYWL_CURSOR_RESIZE ) {
        process_cursor_resize( server, time );
        return;
    }

    /** Otherwise, find the view under the pointer and send the event along. */
    double             sx, sy;
    struct wlr_seat    *seat    = server->seat;
    struct wlr_surface *surface = NULL;
    struct tinywl_view *view    = desktop_view_at( server,
                                                   server->cursor->x, server->cursor->y, &surface, &sx, &sy );

    if ( !view ) {
        /** If there's no view under the cursor, set the cursor image to a
         * default. This is what makes the cursor image appear when you move it
         * around the screen, not over any views. */
        wlr_cursor_set_xcursor( server->cursor, server->cursor_mgr, "default" );
    }

    if ( surface ) {
        /*
         * Send pointer enter and motion events.
         *
         * The enter event gives the surface "pointer focus", which is distinct
         * from keyboard focus. You get pointer focus by moving the pointer over
         * a window.
         *
         * Note that wlroots will avoid sending duplicate enter/motion events if
         * the surface has already has pointer focus or if the client is already
         * aware of the coordinates passed.
         */
        wlr_seat_pointer_notify_enter( seat, surface, sx, sy );
        wlr_seat_pointer_notify_motion( seat, time, sx, sy );
    }
    else {
        /** Clear pointer focus so future button events and such are not sent to
         * the last client to have the cursor over it. */
        wlr_seat_pointer_clear_focus( seat );
    }
}


static void server_cursor_motion( struct wl_listener *listener, void *data ) {
    /** This event is forwarded by the cursor when a pointer emits a _relative_
     * pointer motion event (i.e. a delta) */
    struct tinywl_server            *server = wl_container_of( listener, server, cursor_motion );
    struct wlr_pointer_motion_event *event  = data;

    /** The cursor doesn't move unless we tell it to. The cursor automatically
     * handles constraining the motion to the output layout, as well as any
     * special configuration applied for the specific input device which
     * generated the event. You can pass NULL for the device if you want to move
     * the cursor around without any input. */
    wlr_cursor_move( server->cursor, &event->pointer->base, event->delta_x, event->delta_y );
    process_cursor_motion( server, event->time_msec );
}


static void server_cursor_motion_absolute( struct wl_listener *listener, void *data ) {
    /** This event is forwarded by the cursor when a pointer emits an _absolute_
     * motion event, from 0..1 on each axis. This happens, for example, when
     * wlroots is running under a Wayland window rather than KMS+DRM, and you
     * move the mouse over the window. You could enter the window from any edge,
     * so we have to warp the mouse there. There is also some hardware which
     * emits these events. */
    struct tinywl_server *server = wl_container_of( listener, server, cursor_motion_absolute );
    struct wlr_pointer_motion_absolute_event *event = data;

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


static void server_cursor_button( struct wl_listener *listener, void *data ) {
    /** This event is forwarded by the cursor when a pointer emits a button
     * event. */
    struct tinywl_server            *server = wl_container_of( listener, server, cursor_button );
    struct wlr_pointer_button_event *event  = data;

    /** Notify the client with pointer focus that a button press has occurred */
    wlr_seat_pointer_notify_button( server->seat, event->time_msec, event->button, event->state );
    double             sx, sy;
    struct wlr_surface *surface = NULL;
    struct tinywl_view *view    = desktop_view_at( server, server->cursor->x, server->cursor->y, &surface, &sx, &sy );

    if ( event->state == WLR_BUTTON_RELEASED ) {
        /* If you released any buttons, we exit interactive move/resize mode. */
        reset_cursor_mode( server );
    }

    else {
        /* Focus that client if the button was _pressed_ */
        focus_view( view, surface );
    }
}


static void server_cursor_axis( struct wl_listener *listener, void *data ) {
    /** This event is forwarded by the cursor when a pointer emits an axis event,
     * for example when you move the scroll wheel. */
    struct tinywl_server          *server = wl_container_of( listener, server, cursor_axis );
    struct wlr_pointer_axis_event *event  = data;

    /** Notify the client with pointer focus of the axis event. */
    wlr_seat_pointer_notify_axis( server->seat,
                                  event->time_msec, event->orientation, event->delta,
                                  event->delta_discrete, event->source );
}


static void server_cursor_frame( struct wl_listener *listener, void * ) {
    /** This event is forwarded by the cursor when a pointer emits an frame
     * event. Frame events are sent after regular pointer events to group
     * multiple events together. For instance, two axis events may happen at the
     * same time, in which case a frame event won't be sent in between. */
    struct tinywl_server *server =
        wl_container_of( listener, server, cursor_frame );

    /** Notify the client with pointer focus of the frame event. */
    wlr_seat_pointer_notify_frame( server->seat );
}


static void output_frame( struct wl_listener *listener, void * ) {
    /** This function is called every time an output is ready to display a frame,
     * generally at the output's refresh rate (e.g. 60Hz). */
    struct tinywl_output *output = wl_container_of( listener, output, frame );
    struct wlr_scene     *scene  = output->server->scene;

    struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(
        scene, output->wlr_output );

    /** Render the scene if needed and commit the output */
    wlr_scene_output_commit( scene_output, NULL );

    struct timespec now;

    clock_gettime( CLOCK_MONOTONIC, &now );
    wlr_scene_output_send_frame_done( scene_output, &now );
}


static void output_destroy( struct wl_listener *listener, void * ) {
    struct tinywl_output *output = wl_container_of( listener, output, destroy );

    wl_list_remove( &output->frame.link );
    wl_list_remove( &output->destroy.link );
    wl_list_remove( &output->link );
    free( output );
}


static void server_new_output( struct wl_listener *listener, void *data ) {
    /** This event is raised by the backend when a new output (aka a display or
     * monitor) becomes available. */
    struct tinywl_server *server =
        wl_container_of( listener, server, new_output );
    struct wlr_output *wlr_output = data;

    /** Configures the output created by the backend to use our allocator
    * and our renderer. Must be done once, before commiting the output */
    wlr_output_init_render( wlr_output, server->allocator, server->renderer );

    /** Some backends don't have modes. DRM+KMS does, and we need to set a mode
     * before we can use the output. The mode is a tuple of (width, height,
     * refresh rate), and each monitor supports only a specific set of modes. We
     * just pick the monitor's preferred mode, a more sophisticated compositor
     * would let the user configure it. */
    if ( !wl_list_empty( &wlr_output->modes ) ) {
        struct wlr_output_mode *mode = wlr_output_preferred_mode( wlr_output );
        wlr_output_set_mode( wlr_output, mode );
        wlr_output_enable( wlr_output, true );

        if ( !wlr_output_commit( wlr_output ) ) {
            return;
        }
    }

    /** Allocates and configures our state for this output */
    struct tinywl_output *output =
        calloc( 1, sizeof(struct tinywl_output) );

    output->wlr_output = wlr_output;
    output->server     = server;
    /** Sets up a listener for the frame notify event. */
    output->frame.notify = output_frame;
    wl_signal_add( &wlr_output->events.frame, &output->frame );

    /* Sets up a listener for the destroy notify event. */
    output->destroy.notify = output_destroy;
    wl_signal_add( &wlr_output->events.destroy, &output->destroy );

    wl_list_insert( &server->outputs, &output->link );

    /** Adds this to the output layout. The add_auto function arranges outputs
     * from left-to-right in the order they appear. A more sophisticated
     * compositor would let the user configure the arrangement of outputs in the
     * layout.
     *
     * The output layout utility automatically adds a wl_output global to the
     * display, which Wayland clients can see to find out information about the
     * output (such as DPI, scale factor, manufacturer, etc).
     */
    wlr_output_layout_add_auto( server->output_layout, wlr_output );
}


static void xdg_toplevel_map( struct wl_listener *listener, void * ) {
    /** Called when the surface is mapped, or ready to display on-screen. */
    struct tinywl_view *view = wl_container_of( listener, view, map );

    wl_list_insert( &view->server->views, &view->link );

    focus_view( view, view->xdg_toplevel->base->surface );
}


static void xdg_toplevel_unmap( struct wl_listener *listener, void * ) {
    /** Called when the surface is unmapped, and should no longer be shown. */
    struct tinywl_view *view = wl_container_of( listener, view, unmap );

    wl_list_remove( &view->link );
}


static void xdg_toplevel_destroy( struct wl_listener *listener, void * ) {
    /** Called when the surface is destroyed and should never be shown again. */
    struct tinywl_view *view = wl_container_of( listener, view, destroy );

    wl_list_remove( &view->map.link );
    wl_list_remove( &view->unmap.link );
    wl_list_remove( &view->destroy.link );
    wl_list_remove( &view->request_move.link );
    wl_list_remove( &view->request_resize.link );
    wl_list_remove( &view->request_maximize.link );
    wl_list_remove( &view->request_fullscreen.link );

    free( view );
}


static void begin_interactive( struct tinywl_view *view,
                               enum tinywl_cursor_mode mode, uint32_t edges ) {
    /** This function sets up an interactive move or resize operation, where the
     * compositor stops propegating pointer events to clients and instead
     * consumes them itself, to move or resize windows. */
    struct tinywl_server *server          = view->server;
    struct wlr_surface   *focused_surface =
        server->seat->pointer_state.focused_surface;

    if ( view->xdg_toplevel->base->surface != wlr_surface_get_root_surface( focused_surface ) ) {
        /** Deny move/resize requests from unfocused clients. */
        return;
    }

    server->grabbed_view = view;
    server->cursor_mode  = mode;

    if ( mode == TINYWL_CURSOR_MOVE ) {
        server->grab_x = server->cursor->x - view->x;
        server->grab_y = server->cursor->y - view->y;
    }
    else {
        struct wlr_box geo_box;
        wlr_xdg_surface_get_geometry( view->xdg_toplevel->base, &geo_box );

        double border_x = (view->x + geo_box.x) +
                          ( (edges & WLR_EDGE_RIGHT) ? geo_box.width : 0);
        double border_y = (view->y + geo_box.y) +
                          ( (edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0);
        server->grab_x = server->cursor->x - border_x;
        server->grab_y = server->cursor->y - border_y;

        server->grab_geobox    = geo_box;
        server->grab_geobox.x += view->x;
        server->grab_geobox.y += view->y;

        server->resize_edges = edges;
    }
}


static void xdg_toplevel_request_move( struct wl_listener *listener, void * ) {
    /** This event is raised when a client would like to begin an interactive
     * move, typically because the user clicked on their client-side
     * decorations. Note that a more sophisticated compositor should check the
     * provided serial against a list of button press serials sent to this
     * client, to prevent the client from requesting this whenever they want. */
    struct tinywl_view *view = wl_container_of( listener, view, request_move );

    begin_interactive( view, TINYWL_CURSOR_MOVE, 0 );
}


static void xdg_toplevel_request_resize( struct wl_listener *listener, void *data ) {
    /** This event is raised when a client would like to begin an interactive
     * resize, typically because the user clicked on their client-side
     * decorations. Note that a more sophisticated compositor should check the
     * provided serial against a list of button press serials sent to this
     * client, to prevent the client from requesting this whenever they want. */
    struct wlr_xdg_toplevel_resize_event *event = data;
    struct tinywl_view *view = wl_container_of( listener, view, request_resize );

    begin_interactive( view, TINYWL_CURSOR_RESIZE, event->edges );
}


static void xdg_toplevel_request_maximize( struct wl_listener *listener, void * ) {
    /* This event is raised when a client would like to maximize itself,
     * typically because the user clicked on the maximize button on
     * client-side decorations. tinywl doesn't support maximization, but
     * to conform to xdg-shell protocol we still must send a configure.
     * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */
    struct tinywl_view *toplevel = wl_container_of( listener, toplevel, request_maximize );

    wlr_xdg_surface_schedule_configure( toplevel->xdg_toplevel->base );
}


static void xdg_toplevel_request_fullscreen( struct wl_listener *listener, void * ) {
    /* Just as with request_maximize, we must send a configure here. */
    struct tinywl_view *toplevel = wl_container_of( listener, toplevel, request_fullscreen );

    wlr_xdg_surface_schedule_configure( toplevel->xdg_toplevel->base );
}


static void server_new_xdg_surface( struct wl_listener *listener, void *data ) {
    /**
     * This event is raised when wlr_xdg_shell receives a new xdg surface from a
     * client, either a toplevel (application window) or popup.
     */
    struct tinywl_server   *server      = wl_container_of( listener, server, new_xdg_surface );
    struct wlr_xdg_surface *xdg_surface = data;

    /**
     * We must add xdg popups to the scene graph so they get rendered. The
     * wlroots scene graph provides a helper for this, but to use it we must
     * provide the proper parent scene node of the xdg popup. To enable this,
     * we always set the user data field of xdg_surfaces to the corresponding
     * scene node.
     */
    if ( xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP ) {
        struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface( xdg_surface->popup->parent );
        assert( parent != NULL );
        struct wlr_scene_tree *parent_tree = parent->data;
        xdg_surface->data = wlr_scene_xdg_surface_create( parent_tree, xdg_surface );
        return;
    }

    assert( xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL );

    /** Allocate a tinywl_view for this surface */
    struct tinywl_view *view =
        calloc( 1, sizeof(struct tinywl_view) );

    view->server                = server;
    view->xdg_toplevel          = xdg_surface->toplevel;
    view->scene_tree            = wlr_scene_xdg_surface_create( &view->server->scene->tree, view->xdg_toplevel->base );
    view->scene_tree->node.data = view;
    xdg_surface->data           = view->scene_tree;

    /** Listen to the various events it can emit */
    view->map.notify = xdg_toplevel_map;
    wl_signal_add( &xdg_surface->surface->events.map,     &view->map );
    view->unmap.notify = xdg_toplevel_unmap;
    wl_signal_add( &xdg_surface->surface->events.unmap,   &view->unmap );
    view->destroy.notify = xdg_toplevel_destroy;
    wl_signal_add( &xdg_surface->surface->events.destroy, &view->destroy );

    /** cotd */
    struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel;

    view->request_move.notify = xdg_toplevel_request_move;
    wl_signal_add( &toplevel->events.request_move,       &view->request_move );
    view->request_resize.notify = xdg_toplevel_request_resize;
    wl_signal_add( &toplevel->events.request_resize,     &view->request_resize );
    view->request_maximize.notify = xdg_toplevel_request_maximize;
    wl_signal_add( &toplevel->events.request_maximize,   &view->request_maximize );
    view->request_fullscreen.notify = xdg_toplevel_request_fullscreen;
    wl_signal_add( &toplevel->events.request_fullscreen, &view->request_fullscreen );
}


int startCompositor() {
    wlr_log_init( WLR_ERROR, NULL );

    /** The Wayland display is managed by libwayland. It handles accepting
     * clients from the Unix socket, managing Wayland globals, and so on. */
    server->display = wl_display_create();

    /** The backend is a wlroots feature which abstracts the underlying input and
     * output hardware. The auto-create option will choose the most suitable
     * backend based on the current environment, such as opening an X11 window
     * if an X11 server is running. */
    server->backend = wlr_backend_autocreate( server->display, &server->session );

    /** Auto-creates a renderer, either Pixman, GLES2 or Vulkan for us. The user
     * can also specify a renderer using the WLR_RENDERER env var.
     * The renderer is responsible for defining the various pixel formats it
     * supports for shared memory, this configures that for clients. */
    server->renderer = wlr_renderer_autocreate( server->backend );
    wlr_renderer_init_wl_display( server->renderer, server->display );

    /** Auto-creates an allocator for us.
     * The allocator is the bridge between the renderer and the backend. It
     * handles the buffer creation, allowing wlroots to render onto the
     * screen */
    server->allocator = wlr_allocator_autocreate( server->backend, server->renderer );

    /** This creates some hands-off wlroots interfaces. The compositor is
     * necessary for clients to allocate surfaces and the data device manager
     * handles the clipboard. Each of these wlroots interfaces has room for you
     * to dig your fingers in and play with their behavior if you want. Note that
     * the clients cannot set the selection directly without compositor approval,
     * see the handling of the request_set_selection event below.*/
    wlr_compositor_create( server->display, 5, server->renderer );
    wlr_data_device_manager_create( server->display );

    /** Creates an output layout, which a wlroots utility for working with an
     * arrangement of screens in a physical layout. */
    server->output_layout = wlr_output_layout_create();

    /** Configure a listener to be notified when new outputs are available on the
     * backend. */
    wl_list_init( &server->outputs );
    server->new_output.notify = server_new_output;
    wl_signal_add( &server->backend->events.new_output, &server->new_output );

    /** Create a scene graph. This is a wlroots abstraction that handles all
     * rendering and damage tracking. All the compositor author needs to do
     * is add things that should be rendered to the scene graph at the proper
     * positions and then call wlr_scene_output_commit() to render a frame if
     * necessary.
     */
    server->scene = wlr_scene_create();
    wlr_scene_attach_output_layout( server->scene, server->output_layout );

    /** Set up the xdg-shell. The xdg-shell is a Wayland protocol which is used
     * for application windows. For more detail on shells, refer to my article:
     *
     * https://drewdevault.com/2018/07/29/Wayland-shells.html
     */
    wl_list_init( &server->views );
    server->xdg_shell = wlr_xdg_shell_create( server->display, 3 );
    server->new_xdg_surface.notify = server_new_xdg_surface;
    wl_signal_add( &server->xdg_shell->events.new_surface, &server->new_xdg_surface );

    /*
     * Creates a cursor, which is a wlroots utility for tracking the cursor
     * image shown on screen.
     */
    server->cursor = wlr_cursor_create();
    wlr_cursor_attach_output_layout( server->cursor, server->output_layout );

    /** Creates an xcursor manager, another wlroots utility which loads up
     * Xcursor themes to source cursor images from and makes sure that cursor
     * images are available at all scale factors on the screen (necessary for
     * HiDPI support). We add a cursor theme at scale factor 1 to begin with. */
    server->cursor_mgr = wlr_xcursor_manager_create( NULL, 24 );
    wlr_xcursor_manager_load( server->cursor_mgr, 1 );

    /*
     * wlr_cursor *only* displays an image on screen. It does not move around
     * when the pointer moves. However, we can attach input devices to it, and
     * it will generate aggregate events for all of them. In these events, we
     * can choose how we want to process them, forwarding them to clients and
     * moving the cursor around. More detail on this process is described in my
     * input handling blog post:
     *
     * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
     *
     * And more comments are sprinkled throughout the notify functions above.
     */
    server->cursor_motion.notify = server_cursor_motion;
    wl_signal_add( &server->cursor->events.motion,          &server->cursor_motion );
    server->cursor_motion_absolute.notify = server_cursor_motion_absolute;
    wl_signal_add( &server->cursor->events.motion_absolute, &server->cursor_motion_absolute );
    server->cursor_button.notify = server_cursor_button;
    wl_signal_add( &server->cursor->events.button,          &server->cursor_button );
    server->cursor_axis.notify = server_cursor_axis;
    wl_signal_add( &server->cursor->events.axis,            &server->cursor_axis );
    server->cursor_frame.notify = server_cursor_frame;
    wl_signal_add( &server->cursor->events.frame,           &server->cursor_frame );

    /*
     * Configures a seat, which is a single "seat" at which a user sits and
     * operates the computer. This conceptually includes up to one keyboard,
     * pointer, touch, and drawing tablet device. We also rig up a listener to
     * let us know when new input devices are available on the backend.
     */
    wl_list_init( &server->keyboards );
    server->new_input.notify = server_new_input;
    wl_signal_add( &server->backend->events.new_input,          &server->new_input );
    server->seat = wlr_seat_create( server->display, "seat0" );
    server->request_cursor.notify = seat_request_cursor;
    wl_signal_add( &server->seat->events.request_set_cursor,    &server->request_cursor );
    server->request_set_selection.notify = seat_request_set_selection;
    wl_signal_add( &server->seat->events.request_set_selection, &server->request_set_selection );

    /** Add a Unix socket to the Wayland display. */
    const char *socket = wl_display_add_socket_auto( server->display );

    if ( !socket ) {
        wlr_backend_destroy( server->backend );
        return 0;
    }

    /** Start the backend. This will enumerate outputs and inputs, become the DRM
     * master, etc */
    if ( !wlr_backend_start( server->backend ) ) {
        wlr_backend_destroy( server->backend );
        wl_display_destroy( server->display );
        return 0;
    }

    /** Set the WAYLAND_DISPLAY environment variable to our socket and run the
     * startup command if requested. */
    setenv( "WAYLAND_DISPLAY", socket, true );
    wlr_log( WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket );

    return 1;
}


void runWlEventLoop() {
    /** Run the Wayland event loop. This does not return until you exit the
     * compositor. Starting the backend rigged up all of the necessary event
     * loop configuration to listen to libinput events, DRM events, generate
     * frame events at the refresh rate, and so on. */
    wl_display_run( server->display );
}


void haltWlEventLoop() {
    wl_display_terminate( server->display );
}


void closeCompositor() {
    /** Once wl_display_run returns, we shut down the server-> */
    wl_display_destroy_clients( server->display );
    wl_display_destroy( server->display );
}


void setupEnv() {
    setenv( "XCURSOR_THEME",   "bloom",   true );
    setenv( "XCURSOR_SIZE",    "24",      true );
    setenv( "QT_QPA_PLATFORM", "wayland", true );
    setenv( "QT_SCALE_FACTOR", "1.0",     true );
}
