<commit_before>#include <cairo-xlib.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <X11/cursorfont.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>

#include "runes.h"
#include "window-xlib.h"

#include "config.h"
#include "display.h"
#include "loop.h"
#include "pty-unix.h"
#include "term.h"
#include "window-backend-xlib.h"

struct function_key {
    KeySym sym;
    char *str;
    size_t len;
};

#define RUNES_KEY(sym, str) { sym, str, sizeof(str) - 1 }
static struct function_key keys[] = {
    RUNES_KEY(XK_Up,         "\033[A"),
    RUNES_KEY(XK_Down,       "\033[B"),
    RUNES_KEY(XK_Right,      "\033[C"),
    RUNES_KEY(XK_Left,       "\033[D"),
    RUNES_KEY(XK_Page_Up,    "\033[5~"),
    RUNES_KEY(XK_Page_Down,  "\033[6~"),
    RUNES_KEY(XK_Home,       "\033[H"),
    RUNES_KEY(XK_End,        "\033[F"),
    RUNES_KEY(XK_F1,         "\033OP"),
    RUNES_KEY(XK_F2,         "\033OQ"),
    RUNES_KEY(XK_F3,         "\033OR"),
    RUNES_KEY(XK_F4,         "\033OS"),
    RUNES_KEY(XK_F5,         "\033[15~"),
    RUNES_KEY(XK_F6,         "\033[17~"),
    RUNES_KEY(XK_F7,         "\033[18~"),
    RUNES_KEY(XK_F8,         "\033[19~"),
    RUNES_KEY(XK_F9,         "\033[20~"),
    RUNES_KEY(XK_F10,        "\033[21~"),
    RUNES_KEY(XK_F11,        "\033[23~"),
    RUNES_KEY(XK_F12,        "\033[24~"),
    RUNES_KEY(XK_F13,        "\033[25~"),
    RUNES_KEY(XK_F14,        "\033[26~"),
    RUNES_KEY(XK_F15,        "\033[28~"),
    RUNES_KEY(XK_F16,        "\033[29~"),
    RUNES_KEY(XK_F17,        "\033[31~"),
    RUNES_KEY(XK_F18,        "\033[32~"),
    RUNES_KEY(XK_F19,        "\033[33~"),
    RUNES_KEY(XK_F20,        "\033[34~"),
    RUNES_KEY(XK_BackSpace,  "\x7f"),
    RUNES_KEY(XK_Delete,     "\033[3~"),
    /* XXX keypad keys need to go here too */
    RUNES_KEY(XK_VoidSymbol, "")
};

static struct function_key application_keypad_keys[] = {
    /* XXX i don't have a keypad on my laptop, need to get one for testing */
    RUNES_KEY(XK_VoidSymbol, "")
};

static struct function_key application_cursor_keys[] = {
    RUNES_KEY(XK_Up,    "\033OA"),
    RUNES_KEY(XK_Down,  "\033OB"),
    RUNES_KEY(XK_Right, "\033OC"),
    RUNES_KEY(XK_Left,  "\033OD"),
    /* XXX home/end? */
    RUNES_KEY(XK_VoidSymbol, "")
};
#undef RUNES_KEY

static int runes_window_event_cb(void *t);
static int runes_window_handle_event(RunesTerm *t, XEvent *e);
static Bool runes_window_wants_event(
    Display *dpy, XEvent *event, XPointer arg);
static Bool runes_window_find_flush_events(
    Display *dpy, XEvent *e, XPointer arg);
static void runes_window_resize_window(
    RunesTerm *t, int width, int height);
static void runes_window_flush(RunesTerm *t);
static void runes_window_write_to_pty(
    RunesTerm *t, char *buf, size_t len);
static int runes_window_check_recent(RunesTerm *t);
static void runes_window_delay_cb(void *t);
static void runes_window_visible_scroll(RunesTerm *t, int count);
static void runes_window_visual_bell(RunesTerm *t);
static void runes_window_reset_visual_bell(void *t);
static void runes_window_audible_bell(RunesTerm *t);
static void runes_window_set_urgent(RunesTerm *t);
static void runes_window_clear_urgent(RunesTerm *t);
static void runes_window_paste(RunesTerm *t, Time time);
static void runes_window_start_selection(
    RunesTerm *t, int xpixel, int ypixel);
static void runes_window_start_selection_loc(
    RunesTerm *t, struct vt100_loc *start);
static void runes_window_update_selection(
    RunesTerm *t, int xpixel, int ypixel, Time time);
static void runes_window_update_selection_loc(
    RunesTerm *t, struct vt100_loc *end, Time time);
static void runes_window_acquire_selection(RunesTerm *t, Time time);
static void runes_window_clear_selection(RunesTerm *t);
static void runes_window_handle_key_event(RunesTerm *t, XKeyEvent *e);
static void runes_window_handle_button_event(RunesTerm *t, XButtonEvent *e);
static void runes_window_handle_motion_event(RunesTerm *t, XMotionEvent *e);
static void runes_window_handle_expose_event(RunesTerm *t, XExposeEvent *e);
static void runes_window_handle_configure_event(
    RunesTerm *t, XConfigureEvent *e);
static void runes_window_handle_focus_event(
    RunesTerm *t, XFocusChangeEvent *e);
static void runes_window_handle_selection_notify_event(
    RunesTerm *t, XSelectionEvent *e);
static void runes_window_handle_selection_clear_event(
    RunesTerm *t, XSelectionClearEvent *e);
static void runes_window_handle_selection_request_event(
    RunesTerm *t, XSelectionRequestEvent *e);
static int runes_window_handle_builtin_keypress(
    RunesTerm *t, KeySym sym, XKeyEvent *e);
static int runes_window_handle_builtin_button_press(
    RunesTerm *t, XButtonEvent *e);
static void runes_window_handle_multi_click(RunesTerm *t, XButtonEvent *e);
static void runes_window_beginning_of_word(RunesTerm *t, struct vt100_loc *loc);
static void runes_window_end_of_word(RunesTerm *t, struct vt100_loc *loc);
static int runes_window_is_word_char(char *buf, size_t len);
static void runes_window_multi_click_cb(void *t);
static struct function_key *runes_window_find_key_sequence(
    RunesTerm *t, KeySym sym);
static struct vt100_loc runes_window_get_mouse_position(
    RunesTerm *t, int xpixel, int ypixel);

RunesWindow *runes_window_new(RunesWindowBackend *wb)
{
    RunesWindow *w;

    w = calloc(1, sizeof(RunesWindow));
    w->wb = wb;

    return w;
}

void runes_window_create_window(RunesTerm *t, int argc, char *argv[])
{
    RunesWindow *w = t->w;
    pid_t pid;
    XClassHint class_hints = { RUNES_PROGRAM_NAME, RUNES_PROGRAM_NAME };
    XWMHints wm_hints;
    XSizeHints normal_hints;
    double bg_r, bg_g, bg_b;
    XColor bgcolor;
    XIM im;
    Cursor cursor;
    double mouse_r, mouse_g, mouse_b;
    XColor cursor_fg, cursor_bg;
    Visual *vis;
    cairo_surface_t *surface;

    wm_hints.flags = InputHint | StateHint;
    wm_hints.input = True;
    wm_hints.initial_state = NormalState;

    normal_hints.flags = PMinSize | PResizeInc | PBaseSize;

    normal_hints.min_width   = t->display->fontx + 4;
    normal_hints.min_height  = t->display->fonty + 4;
    normal_hints.width_inc   = t->display->fontx;
    normal_hints.height_inc  = t->display->fonty;
    normal_hints.base_width  = t->display->fontx * t->config->default_cols + 4;
    normal_hints.base_height = t->display->fonty * t->config->default_rows + 4;

    cairo_pattern_get_rgba(t->config->bgdefault, &bg_r, &bg_g, &bg_b, NULL);
    bgcolor.red   = bg_r * 65535;
    bgcolor.green = bg_g * 65535;
    bgcolor.blue  = bg_b * 65535;

    XAllocColor(
        w->wb->dpy, DefaultColormap(w->wb->dpy, DefaultScreen(w->wb->dpy)),
        &bgcolor);
    w->border_w = XCreateSimpleWindow(
        w->wb->dpy, DefaultRootWindow(w->wb->dpy),
        0, 0, normal_hints.base_width, normal_hints.base_height,
        0, bgcolor.pixel, bgcolor.pixel);
    w->w = XCreateSimpleWindow(
        w->wb->dpy, w->border_w,
        2, 2, normal_hints.base_width - 4, normal_hints.base_height - 4,
        0, bgcolor.pixel, bgcolor.pixel);

    im = XOpenIM(w->wb->dpy, NULL, NULL, NULL);
    w->ic = XCreateIC(
        im,
        XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
        XNClientWindow, w->w,
        XNFocusWindow, w->w,
        NULL
    );
    if (w->ic == NULL) {
        runes_die("failed");
    }

    XSetWMProtocols(
        w->wb->dpy, w->border_w, w->wb->atoms, RUNES_NUM_PROTOCOL_ATOMS);

    Xutf8SetWMProperties(
        w->wb->dpy, w->border_w, RUNES_PROGRAM_NAME, RUNES_PROGRAM_NAME,
        argv, argc,
        &normal_hints, &wm_hints, &class_hints);

    pid = getpid();
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_PID],
        XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);

    runes_window_set_icon_name(t, RUNES_PROGRAM_NAME, 5);
    runes_window_set_window_title(t, RUNES_PROGRAM_NAME, 5);

    cursor = XCreateFontCursor(w->wb->dpy, XC_xterm);
    cairo_pattern_get_rgba(
        t->config->mousecursorcolor, &mouse_r, &mouse_g, &mouse_b, NULL);
    cursor_fg.red   = (unsigned short)(mouse_r * 65535);
    cursor_fg.green = (unsigned short)(mouse_g * 65535);
    cursor_fg.blue  = (unsigned short)(mouse_b * 65535);
    if ((mouse_r + mouse_g + mouse_b) / 3.0 > 0.5) {
        cursor_bg.red = cursor_bg.green = cursor_bg.blue = 0;
    }
    else {
        cursor_bg.red = cursor_bg.green = cursor_bg.blue = 65535;
    }
    XRecolorCursor(w->wb->dpy, cursor, &cursor_fg, &cursor_bg);
    XDefineCursor(w->wb->dpy, w->w, cursor);

    vis = DefaultVisual(w->wb->dpy, DefaultScreen(w->wb->dpy));
    surface = cairo_xlib_surface_create(
        w->wb->dpy, w->w, vis, normal_hints.base_width,
        normal_hints.base_height);
    w->backend_cr = cairo_create(surface);
    cairo_surface_destroy(surface);

    XMapWindow(w->wb->dpy, w->w);
    XMapWindow(w->wb->dpy, w->border_w);
}

void runes_window_init_loop(RunesTerm *t, RunesLoop *loop)
{
    RunesWindow *w = t->w;
    unsigned long xim_mask, common_mask;

    XGetICValues(w->ic, XNFilterEvents, &xim_mask, NULL);
    /* we always want to receive keyboard events, and enter/leave window events
     * are what allows keyboard focus switching to work when the mouse is over
     * the window but hidden, for whatever reason */
    common_mask = KeyPressMask|EnterWindowMask|LeaveWindowMask;

    /* the top level window is the only one that needs to worry about window
     * size and focus changes */
    XSelectInput(
        w->wb->dpy, w->border_w,
        xim_mask|common_mask|StructureNotifyMask|FocusChangeMask);
    /* we only care about mouse events if they are over the actual terminal
     * area, and the terminal area is the only area we need to redraw, so it's
     * the only thing we care about exposure events for */
    XSelectInput(
        w->wb->dpy, w->w,
        xim_mask|common_mask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|ExposureMask);
    XSetICFocus(w->ic);

    runes_term_refcnt_inc(t);
    runes_loop_start_work(
        loop, XConnectionNumber(w->wb->dpy), t, runes_window_event_cb);
}

void runes_window_request_flush(RunesTerm *t)
{
    RunesWindow *w = t->w;
    XEvent e;

    e.xclient.type = ClientMessage;
    e.xclient.window = w->w;
    e.xclient.format = 32;
    e.xclient.data.l[0] = w->wb->atoms[RUNES_ATOM_RUNES_FLUSH];

    XSendEvent(w->wb->dpy, w->w, False, NoEventMask, &e);
    XFlush(w->wb->dpy);
}

void runes_window_request_close(RunesTerm *t)
{
    RunesWindow *w = t->w;
    XEvent e;

    e.xclient.type = ClientMessage;
    e.xclient.window = w->w;
    e.xclient.message_type = w->wb->atoms[RUNES_ATOM_WM_PROTOCOLS];
    e.xclient.format = 32;
    e.xclient.data.l[0] = w->wb->atoms[RUNES_ATOM_WM_DELETE_WINDOW];
    e.xclient.data.l[1] = CurrentTime;

    XSendEvent(w->wb->dpy, w->w, False, NoEventMask, &e);
    XFlush(w->wb->dpy);
}

unsigned long runes_window_get_window_id(RunesTerm *t)
{
    RunesWindow *w = t->w;

    return (unsigned long)w->w;
}

void runes_window_get_size(RunesTerm *t, int *xpixel, int *ypixel)
{
    RunesWindow *w = t->w;
    cairo_surface_t *surface;

    surface = cairo_get_target(w->backend_cr);
    *xpixel = cairo_xlib_surface_get_width(surface);
    *ypixel = cairo_xlib_surface_get_height(surface);
}

void runes_window_set_icon_name(RunesTerm *t, char *name, size_t len)
{
    RunesWindow *w = t->w;

    XChangeProperty(
        w->wb->dpy, w->border_w, XA_WM_ICON_NAME,
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_ICON_NAME],
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
}

void runes_window_set_window_title(RunesTerm *t, char *name, size_t len)
{
    RunesWindow *w = t->w;

    XChangeProperty(
        w->wb->dpy, w->border_w, XA_WM_NAME,
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_NAME],
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
}

void runes_window_delete(RunesWindow *w)
{
    XIM im;

    cairo_destroy(w->backend_cr);
    im = XIMOfIC(w->ic);
    XDestroyIC(w->ic);
    XCloseIM(im);
    XDestroyWindow(w->wb->dpy, w->w);
    XDestroyWindow(w->wb->dpy, w->border_w);
    XFlush(w->wb->dpy);

    free(w);
}

static int runes_window_event_cb(void *t)
{
    RunesWindow *w = ((RunesTerm *)t)->w;
    XEvent e;
    int should_close = 0;

    while (XCheckIfEvent(w->wb->dpy, &e, runes_window_wants_event, t)) {
        should_close = runes_window_handle_event(((RunesTerm *)t), &e);
        if (should_close) {
            break;
        }
    }

    if (should_close) {
        runes_pty_request_close(t);
        runes_term_refcnt_dec(t);
    }

    return !should_close;
}

static int runes_window_handle_event(RunesTerm *t, XEvent *e)
{
    RunesWindow *w = ((RunesTerm *)t)->w;
    int should_close = 0;

    if (!XFilterEvent(e, None)) {
        switch (e->type) {
        case KeyPress:
        case KeyRelease:
            runes_window_handle_key_event(t, &e->xkey);
            break;
        case ButtonPress:
        case ButtonRelease:
            runes_window_handle_button_event(t, &e->xbutton);
            break;
        case MotionNotify:
            runes_window_handle_motion_event(t, &e->xmotion);
            break;
        case Expose:
            runes_window_handle_expose_event(t, &e->xexpose);
            break;
        case ConfigureNotify:
            runes_window_handle_configure_event(t, &e->xconfigure);
            break;
        case FocusIn:
        case FocusOut:
            runes_window_handle_focus_event(t, &e->xfocus);
            break;
        case SelectionNotify:
            runes_window_handle_selection_notify_event(
                t, &e->xselection);
            break;
        case SelectionRequest:
            runes_window_handle_selection_request_event(
                t, &e->xselectionrequest);
            break;
        case SelectionClear:
            runes_window_handle_selection_clear_event(
                t, &e->xselectionclear);
            break;
        case ClientMessage: {
            Atom a = e->xclient.data.l[0];
            if (a == w->wb->atoms[RUNES_ATOM_WM_DELETE_WINDOW]) {
                should_close = 1;
            }
            else if (a == w->wb->atoms[RUNES_ATOM_NET_WM_PING]) {
                e->xclient.window = DefaultRootWindow(w->wb->dpy);
                XSendEvent(
                    w->wb->dpy, e->xclient.window, False,
                    SubstructureNotifyMask | SubstructureRedirectMask,
                    e
                );
            }
            else if (a == w->wb->atoms[RUNES_ATOM_RUNES_FLUSH]) {
                Bool res = True;

                while (res) {
                    res = XCheckIfEvent(
                        w->wb->dpy, e, runes_window_find_flush_events,
                        (XPointer)w);
                }
                runes_window_flush(t);
            }
            break;
        }
        default:
            break;
        }
    }

    return should_close;
}

static Bool runes_window_wants_event(Display *dpy, XEvent *event, XPointer arg)
{
    RunesWindow *w = ((RunesTerm *)arg)->w;
    Window event_window = ((XAnyEvent*)event)->window;

    UNUSED(dpy);

    return event_window == w->w || event_window == w->border_w;
}

static Bool runes_window_find_flush_events(
    Display *dpy, XEvent *event, XPointer arg)
{
    RunesWindow *w = (RunesWindow *)arg;
    Window event_window = ((XAnyEvent*)event)->window;
    Atom a = event->xclient.data.l[0];

    UNUSED(dpy);

    if (event_window != w->w && event_window != w->border_w) {
        return False;
    }

    if (event->type != ClientMessage) {
        return False;
    }

    return a == w->wb->atoms[RUNES_ATOM_RUNES_FLUSH];
}

static void runes_window_resize_window(
    RunesTerm *t, int width, int height)
{
    RunesWindow *w = t->w;

    /* XXX no idea why shrinking the window dimensions to 0 makes xlib think
     * that the dimension is 65535 */
    if (width < 1 || width >= 65535) {
        width = 1;
    }
    if (height < 1 || height >= 65535) {
        height = 1;
    }<commit_msg>this is unnecessary now that we have rate limiting<commit_after>#include <cairo-xlib.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <X11/cursorfont.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>

#include "runes.h"
#include "window-xlib.h"

#include "config.h"
#include "display.h"
#include "loop.h"
#include "pty-unix.h"
#include "term.h"
#include "window-backend-xlib.h"

struct function_key {
    KeySym sym;
    char *str;
    size_t len;
};

#define RUNES_KEY(sym, str) { sym, str, sizeof(str) - 1 }
static struct function_key keys[] = {
    RUNES_KEY(XK_Up,         "\033[A"),
    RUNES_KEY(XK_Down,       "\033[B"),
    RUNES_KEY(XK_Right,      "\033[C"),
    RUNES_KEY(XK_Left,       "\033[D"),
    RUNES_KEY(XK_Page_Up,    "\033[5~"),
    RUNES_KEY(XK_Page_Down,  "\033[6~"),
    RUNES_KEY(XK_Home,       "\033[H"),
    RUNES_KEY(XK_End,        "\033[F"),
    RUNES_KEY(XK_F1,         "\033OP"),
    RUNES_KEY(XK_F2,         "\033OQ"),
    RUNES_KEY(XK_F3,         "\033OR"),
    RUNES_KEY(XK_F4,         "\033OS"),
    RUNES_KEY(XK_F5,         "\033[15~"),
    RUNES_KEY(XK_F6,         "\033[17~"),
    RUNES_KEY(XK_F7,         "\033[18~"),
    RUNES_KEY(XK_F8,         "\033[19~"),
    RUNES_KEY(XK_F9,         "\033[20~"),
    RUNES_KEY(XK_F10,        "\033[21~"),
    RUNES_KEY(XK_F11,        "\033[23~"),
    RUNES_KEY(XK_F12,        "\033[24~"),
    RUNES_KEY(XK_F13,        "\033[25~"),
    RUNES_KEY(XK_F14,        "\033[26~"),
    RUNES_KEY(XK_F15,        "\033[28~"),
    RUNES_KEY(XK_F16,        "\033[29~"),
    RUNES_KEY(XK_F17,        "\033[31~"),
    RUNES_KEY(XK_F18,        "\033[32~"),
    RUNES_KEY(XK_F19,        "\033[33~"),
    RUNES_KEY(XK_F20,        "\033[34~"),
    RUNES_KEY(XK_BackSpace,  "\x7f"),
    RUNES_KEY(XK_Delete,     "\033[3~"),
    /* XXX keypad keys need to go here too */
    RUNES_KEY(XK_VoidSymbol, "")
};

static struct function_key application_keypad_keys[] = {
    /* XXX i don't have a keypad on my laptop, need to get one for testing */
    RUNES_KEY(XK_VoidSymbol, "")
};

static struct function_key application_cursor_keys[] = {
    RUNES_KEY(XK_Up,    "\033OA"),
    RUNES_KEY(XK_Down,  "\033OB"),
    RUNES_KEY(XK_Right, "\033OC"),
    RUNES_KEY(XK_Left,  "\033OD"),
    /* XXX home/end? */
    RUNES_KEY(XK_VoidSymbol, "")
};
#undef RUNES_KEY

static int runes_window_event_cb(void *t);
static int runes_window_handle_event(RunesTerm *t, XEvent *e);
static Bool runes_window_wants_event(
    Display *dpy, XEvent *event, XPointer arg);
static void runes_window_resize_window(
    RunesTerm *t, int width, int height);
static void runes_window_flush(RunesTerm *t);
static void runes_window_write_to_pty(
    RunesTerm *t, char *buf, size_t len);
static int runes_window_check_recent(RunesTerm *t);
static void runes_window_delay_cb(void *t);
static void runes_window_visible_scroll(RunesTerm *t, int count);
static void runes_window_visual_bell(RunesTerm *t);
static void runes_window_reset_visual_bell(void *t);
static void runes_window_audible_bell(RunesTerm *t);
static void runes_window_set_urgent(RunesTerm *t);
static void runes_window_clear_urgent(RunesTerm *t);
static void runes_window_paste(RunesTerm *t, Time time);
static void runes_window_start_selection(
    RunesTerm *t, int xpixel, int ypixel);
static void runes_window_start_selection_loc(
    RunesTerm *t, struct vt100_loc *start);
static void runes_window_update_selection(
    RunesTerm *t, int xpixel, int ypixel, Time time);
static void runes_window_update_selection_loc(
    RunesTerm *t, struct vt100_loc *end, Time time);
static void runes_window_acquire_selection(RunesTerm *t, Time time);
static void runes_window_clear_selection(RunesTerm *t);
static void runes_window_handle_key_event(RunesTerm *t, XKeyEvent *e);
static void runes_window_handle_button_event(RunesTerm *t, XButtonEvent *e);
static void runes_window_handle_motion_event(RunesTerm *t, XMotionEvent *e);
static void runes_window_handle_expose_event(RunesTerm *t, XExposeEvent *e);
static void runes_window_handle_configure_event(
    RunesTerm *t, XConfigureEvent *e);
static void runes_window_handle_focus_event(
    RunesTerm *t, XFocusChangeEvent *e);
static void runes_window_handle_selection_notify_event(
    RunesTerm *t, XSelectionEvent *e);
static void runes_window_handle_selection_clear_event(
    RunesTerm *t, XSelectionClearEvent *e);
static void runes_window_handle_selection_request_event(
    RunesTerm *t, XSelectionRequestEvent *e);
static int runes_window_handle_builtin_keypress(
    RunesTerm *t, KeySym sym, XKeyEvent *e);
static int runes_window_handle_builtin_button_press(
    RunesTerm *t, XButtonEvent *e);
static void runes_window_handle_multi_click(RunesTerm *t, XButtonEvent *e);
static void runes_window_beginning_of_word(RunesTerm *t, struct vt100_loc *loc);
static void runes_window_end_of_word(RunesTerm *t, struct vt100_loc *loc);
static int runes_window_is_word_char(char *buf, size_t len);
static void runes_window_multi_click_cb(void *t);
static struct function_key *runes_window_find_key_sequence(
    RunesTerm *t, KeySym sym);
static struct vt100_loc runes_window_get_mouse_position(
    RunesTerm *t, int xpixel, int ypixel);

RunesWindow *runes_window_new(RunesWindowBackend *wb)
{
    RunesWindow *w;

    w = calloc(1, sizeof(RunesWindow));
    w->wb = wb;

    return w;
}

void runes_window_create_window(RunesTerm *t, int argc, char *argv[])
{
    RunesWindow *w = t->w;
    pid_t pid;
    XClassHint class_hints = { RUNES_PROGRAM_NAME, RUNES_PROGRAM_NAME };
    XWMHints wm_hints;
    XSizeHints normal_hints;
    double bg_r, bg_g, bg_b;
    XColor bgcolor;
    XIM im;
    Cursor cursor;
    double mouse_r, mouse_g, mouse_b;
    XColor cursor_fg, cursor_bg;
    Visual *vis;
    cairo_surface_t *surface;

    wm_hints.flags = InputHint | StateHint;
    wm_hints.input = True;
    wm_hints.initial_state = NormalState;

    normal_hints.flags = PMinSize | PResizeInc | PBaseSize;

    normal_hints.min_width   = t->display->fontx + 4;
    normal_hints.min_height  = t->display->fonty + 4;
    normal_hints.width_inc   = t->display->fontx;
    normal_hints.height_inc  = t->display->fonty;
    normal_hints.base_width  = t->display->fontx * t->config->default_cols + 4;
    normal_hints.base_height = t->display->fonty * t->config->default_rows + 4;

    cairo_pattern_get_rgba(t->config->bgdefault, &bg_r, &bg_g, &bg_b, NULL);
    bgcolor.red   = bg_r * 65535;
    bgcolor.green = bg_g * 65535;
    bgcolor.blue  = bg_b * 65535;

    XAllocColor(
        w->wb->dpy, DefaultColormap(w->wb->dpy, DefaultScreen(w->wb->dpy)),
        &bgcolor);
    w->border_w = XCreateSimpleWindow(
        w->wb->dpy, DefaultRootWindow(w->wb->dpy),
        0, 0, normal_hints.base_width, normal_hints.base_height,
        0, bgcolor.pixel, bgcolor.pixel);
    w->w = XCreateSimpleWindow(
        w->wb->dpy, w->border_w,
        2, 2, normal_hints.base_width - 4, normal_hints.base_height - 4,
        0, bgcolor.pixel, bgcolor.pixel);

    im = XOpenIM(w->wb->dpy, NULL, NULL, NULL);
    w->ic = XCreateIC(
        im,
        XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
        XNClientWindow, w->w,
        XNFocusWindow, w->w,
        NULL
    );
    if (w->ic == NULL) {
        runes_die("failed");
    }

    XSetWMProtocols(
        w->wb->dpy, w->border_w, w->wb->atoms, RUNES_NUM_PROTOCOL_ATOMS);

    Xutf8SetWMProperties(
        w->wb->dpy, w->border_w, RUNES_PROGRAM_NAME, RUNES_PROGRAM_NAME,
        argv, argc,
        &normal_hints, &wm_hints, &class_hints);

    pid = getpid();
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_PID],
        XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);

    runes_window_set_icon_name(t, RUNES_PROGRAM_NAME, 5);
    runes_window_set_window_title(t, RUNES_PROGRAM_NAME, 5);

    cursor = XCreateFontCursor(w->wb->dpy, XC_xterm);
    cairo_pattern_get_rgba(
        t->config->mousecursorcolor, &mouse_r, &mouse_g, &mouse_b, NULL);
    cursor_fg.red   = (unsigned short)(mouse_r * 65535);
    cursor_fg.green = (unsigned short)(mouse_g * 65535);
    cursor_fg.blue  = (unsigned short)(mouse_b * 65535);
    if ((mouse_r + mouse_g + mouse_b) / 3.0 > 0.5) {
        cursor_bg.red = cursor_bg.green = cursor_bg.blue = 0;
    }
    else {
        cursor_bg.red = cursor_bg.green = cursor_bg.blue = 65535;
    }
    XRecolorCursor(w->wb->dpy, cursor, &cursor_fg, &cursor_bg);
    XDefineCursor(w->wb->dpy, w->w, cursor);

    vis = DefaultVisual(w->wb->dpy, DefaultScreen(w->wb->dpy));
    surface = cairo_xlib_surface_create(
        w->wb->dpy, w->w, vis, normal_hints.base_width,
        normal_hints.base_height);
    w->backend_cr = cairo_create(surface);
    cairo_surface_destroy(surface);

    XMapWindow(w->wb->dpy, w->w);
    XMapWindow(w->wb->dpy, w->border_w);
}

void runes_window_init_loop(RunesTerm *t, RunesLoop *loop)
{
    RunesWindow *w = t->w;
    unsigned long xim_mask, common_mask;

    XGetICValues(w->ic, XNFilterEvents, &xim_mask, NULL);
    /* we always want to receive keyboard events, and enter/leave window events
     * are what allows keyboard focus switching to work when the mouse is over
     * the window but hidden, for whatever reason */
    common_mask = KeyPressMask|EnterWindowMask|LeaveWindowMask;

    /* the top level window is the only one that needs to worry about window
     * size and focus changes */
    XSelectInput(
        w->wb->dpy, w->border_w,
        xim_mask|common_mask|StructureNotifyMask|FocusChangeMask);
    /* we only care about mouse events if they are over the actual terminal
     * area, and the terminal area is the only area we need to redraw, so it's
     * the only thing we care about exposure events for */
    XSelectInput(
        w->wb->dpy, w->w,
        xim_mask|common_mask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|ExposureMask);
    XSetICFocus(w->ic);

    runes_term_refcnt_inc(t);
    runes_loop_start_work(
        loop, XConnectionNumber(w->wb->dpy), t, runes_window_event_cb);
}

void runes_window_request_flush(RunesTerm *t)
{
    RunesWindow *w = t->w;
    XEvent e;

    e.xclient.type = ClientMessage;
    e.xclient.window = w->w;
    e.xclient.format = 32;
    e.xclient.data.l[0] = w->wb->atoms[RUNES_ATOM_RUNES_FLUSH];

    XSendEvent(w->wb->dpy, w->w, False, NoEventMask, &e);
    XFlush(w->wb->dpy);
}

void runes_window_request_close(RunesTerm *t)
{
    RunesWindow *w = t->w;
    XEvent e;

    e.xclient.type = ClientMessage;
    e.xclient.window = w->w;
    e.xclient.message_type = w->wb->atoms[RUNES_ATOM_WM_PROTOCOLS];
    e.xclient.format = 32;
    e.xclient.data.l[0] = w->wb->atoms[RUNES_ATOM_WM_DELETE_WINDOW];
    e.xclient.data.l[1] = CurrentTime;

    XSendEvent(w->wb->dpy, w->w, False, NoEventMask, &e);
    XFlush(w->wb->dpy);
}

unsigned long runes_window_get_window_id(RunesTerm *t)
{
    RunesWindow *w = t->w;

    return (unsigned long)w->w;
}

void runes_window_get_size(RunesTerm *t, int *xpixel, int *ypixel)
{
    RunesWindow *w = t->w;
    cairo_surface_t *surface;

    surface = cairo_get_target(w->backend_cr);
    *xpixel = cairo_xlib_surface_get_width(surface);
    *ypixel = cairo_xlib_surface_get_height(surface);
}

void runes_window_set_icon_name(RunesTerm *t, char *name, size_t len)
{
    RunesWindow *w = t->w;

    XChangeProperty(
        w->wb->dpy, w->border_w, XA_WM_ICON_NAME,
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_ICON_NAME],
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
}

void runes_window_set_window_title(RunesTerm *t, char *name, size_t len)
{
    RunesWindow *w = t->w;

    XChangeProperty(
        w->wb->dpy, w->border_w, XA_WM_NAME,
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
    XChangeProperty(
        w->wb->dpy, w->border_w, w->wb->atoms[RUNES_ATOM_NET_WM_NAME],
        w->wb->atoms[RUNES_ATOM_UTF8_STRING], 8, PropModeReplace,
        (unsigned char *)name, len);
}

void runes_window_delete(RunesWindow *w)
{
    XIM im;

    cairo_destroy(w->backend_cr);
    im = XIMOfIC(w->ic);
    XDestroyIC(w->ic);
    XCloseIM(im);
    XDestroyWindow(w->wb->dpy, w->w);
    XDestroyWindow(w->wb->dpy, w->border_w);
    XFlush(w->wb->dpy);

    free(w);
}

static int runes_window_event_cb(void *t)
{
    RunesWindow *w = ((RunesTerm *)t)->w;
    XEvent e;
    int should_close = 0;

    while (XCheckIfEvent(w->wb->dpy, &e, runes_window_wants_event, t)) {
        should_close = runes_window_handle_event(((RunesTerm *)t), &e);
        if (should_close) {
            break;
        }
    }

    if (should_close) {
        runes_pty_request_close(t);
        runes_term_refcnt_dec(t);
    }

    return !should_close;
}

static int runes_window_handle_event(RunesTerm *t, XEvent *e)
{
    RunesWindow *w = ((RunesTerm *)t)->w;
    int should_close = 0;

    if (!XFilterEvent(e, None)) {
        switch (e->type) {
        case KeyPress:
        case KeyRelease:
            runes_window_handle_key_event(t, &e->xkey);
            break;
        case ButtonPress:
        case ButtonRelease:
            runes_window_handle_button_event(t, &e->xbutton);
            break;
        case MotionNotify:
            runes_window_handle_motion_event(t, &e->xmotion);
            break;
        case Expose:
            runes_window_handle_expose_event(t, &e->xexpose);
            break;
        case ConfigureNotify:
            runes_window_handle_configure_event(t, &e->xconfigure);
            break;
        case FocusIn:
        case FocusOut:
            runes_window_handle_focus_event(t, &e->xfocus);
            break;
        case SelectionNotify:
            runes_window_handle_selection_notify_event(
                t, &e->xselection);
            break;
        case SelectionRequest:
            runes_window_handle_selection_request_event(
                t, &e->xselectionrequest);
            break;
        case SelectionClear:
            runes_window_handle_selection_clear_event(
                t, &e->xselectionclear);
            break;
        case ClientMessage: {
            Atom a = e->xclient.data.l[0];
            if (a == w->wb->atoms[RUNES_ATOM_WM_DELETE_WINDOW]) {
                should_close = 1;
            }
            else if (a == w->wb->atoms[RUNES_ATOM_NET_WM_PING]) {
                e->xclient.window = DefaultRootWindow(w->wb->dpy);
                XSendEvent(
                    w->wb->dpy, e->xclient.window, False,
                    SubstructureNotifyMask | SubstructureRedirectMask,
                    e
                );
            }
            else if (a == w->wb->atoms[RUNES_ATOM_RUNES_FLUSH]) {
                runes_window_flush(t);
            }
            break;
        }
        default:
            break;
        }
    }

    return should_close;
}

static Bool runes_window_wants_event(Display *dpy, XEvent *event, XPointer arg)
{
    RunesWindow *w = ((RunesTerm *)arg)->w;
    Window event_window = ((XAnyEvent*)event)->window;

    UNUSED(dpy);

    return event_window == w->w || event_window == w->border_w;
}

static void runes_window_resize_window(
    RunesTerm *t, int width, int height)
{
    RunesWindow *w = t->w;

    /* XXX no idea why shrinking the window dimensions to 0 makes xlib think
     * that the dimension is 65535 */
    if (width < 1 || width >= 65535) {
        width = 1;
    }
    if (height < 1 || height >= 65535) {
        height = 1;
    }
{'fraction_non_alphanumeric': 0.1250701240416381, 'fraction_numerical': 0.017982920900081033, 'mean_word_length': 3.0880366925723024, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 396, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3171671', 'n_tokens_mistral': 12434, 'n_tokens_neox': 11807, 'n_words': 2311}