/* See LICENSE for license details. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE		/* memfd_create, pipe2 */
#endif

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/timerfd.h>

#include <linux/input-event-codes.h>
#include <fontconfig/fontconfig.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>

#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include "fractional-scale-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "cursor-shape-client-protocol.h"
#include "system-bell-client-protocol.h"
#include "primary-selection-client-protocol.h"
#include "text-input-client-protocol.h"

#include "st.h"
#include "win.h"
#include "hb.h"

char *argv0;
#include "arg.h"

/* Types used by config.h. */
typedef struct {
	uint mod;
	KeySym keysym;
	void (*func)(const Arg *);
	const Arg arg;
} Shortcut;

typedef struct {
	uint mod;
	uint button;
	void (*func)(const Arg *);
	const Arg arg;
	uint release;
} MouseShortcut;

typedef struct {
	KeySym k;
	uint mask;
	char *s;
	/* three-valued logic variables: 0 indifferent, 1 on, -1 off */
	signed char appkey;    /* application keypad */
	signed char appcursor; /* application cursor */
} Key;

static void clippaste(const Arg *);
static void numlock(const Arg *);
static void selpaste(const Arg *);
static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
static void ttysend(const Arg *) __attribute__((unused));

#include "config.h"

#define IS_SET(flag)		((win.mode & (flag)) != 0)
/* How long a printable key waits for an input method to claim it (ms). */
#define IMEWAIT 20
#define TRUERED(x)		(((x) & 0xff0000) >> 16)
#define TRUEGREEN(x)		(((x) & 0xff00) >> 8)
#define TRUEBLUE(x)		((x) & 0xff)

/* All geometry in this backend is in device (buffer) pixels, except for the
 * logical window size we get from xdg-shell. */
typedef struct {
	int tw, th;	/* terminal area in buffer pixels */
	int w, h;	/* buffer size */
	int lw, lh;	/* logical window size */
	int hborderpx, vborderpx;
	int ch;		/* cell height */
	int cw;		/* cell width */
	int mode;	/* window state/mode flags */
	int cursor;	/* cursor style */
} TermWindow;

typedef struct {
	char *primary;
	struct timespec tclick1;
	struct timespec tclick2;
} Selection;

typedef struct {
	FT_Face face;
	FcPattern *pattern;
	int height, width;
	int ascent, descent;
	int badslant, badweight;
	double pxsize;		/* device pixels */
	double logicalpx;	/* logical pixels */
} Font;

typedef struct {
	uint32_t *col;
	size_t collen;
	Font font, bfont, ifont, ibfont;
} DC;

typedef struct {
	FT_Face face;
	int flags;
	Rune unicodep;
} Fontcache;

typedef struct {
	int type;		/* 0 motion, 1 press, 2 release */
	int x, y;		/* buffer pixels */
	uint button;
	uint state;
	uint32_t serial;
	uint32_t time;
} MouseEvent;

typedef struct {
	struct wl_data_offer *data;
	struct zwp_primary_selection_offer_v1 *primary;
	char **mimes;
	int nmimes;
} Offer;

typedef struct {
	struct wl_data_source *data;
	struct zwp_primary_selection_source_v1 *primary;
	char *text;
} Source;

typedef struct Transfer {
	struct Transfer *next;
	int fd;
	int writing;
	char *buf;
	size_t len, off, cap;
} Transfer;

/* Two buffers, alternating.  KWin keeps the current buffer alive as the
 * window content and only releases it once another one is committed, so a
 * single buffer would stall after the first frame. */
static struct {
	struct wl_buffer *buffer[2];
	uint32_t *data[2];
	uint32_t *target;	/* buffer currently being drawn into */
	int stride;		/* bytes */
	size_t size;
	int busy[2];
	int cur;		/* index of the buffer we draw into next */
	int drawn;
} fb;

/* Globals */
static DC dc;
static TermWindow win;
static Selection xsel;
static GlyphSpec *specbuf = NULL;

static FT_Library ftlib;
static Fontcache *frc = NULL;
static int frclen = 0;
static int frccap = 0;
static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
static int fontsloaded = 0;

static struct wl_display *display = NULL;
static struct wl_registry *registry = NULL;
static struct wl_compositor *compositor = NULL;
static struct wl_shm *shm = NULL;
static struct xdg_wm_base *wm_base = NULL;
static struct wl_seat *seat = NULL;
static struct wl_data_device_manager *data_manager = NULL;
static struct zwp_primary_selection_device_manager_v1 *primary_manager = NULL;
static struct zwp_text_input_manager_v3 *text_input_manager = NULL;
static struct wp_cursor_shape_manager_v1 *cursor_shape_manager = NULL;
static struct wp_fractional_scale_manager_v1 *fractional_manager = NULL;
static struct wp_viewporter *viewporter = NULL;
static struct xdg_system_bell_v1 *systembell = NULL;

static struct wl_surface *surface = NULL;
static struct xdg_surface *xdg_surface = NULL;
static struct xdg_toplevel *xdg_toplevel = NULL;
static struct wp_fractional_scale_v1 *fractional = NULL;
static struct wp_viewport *viewport = NULL;

static struct wl_keyboard *keyboard = NULL;
static struct wl_pointer *pointer = NULL;
static struct wp_cursor_shape_device_v1 *cursor_shape = NULL;
static struct wl_data_device *data_device = NULL;
static struct zwp_primary_selection_device_v1 *primary_device = NULL;
static struct zwp_text_input_v3 *text_input = NULL;

static Offer *clipboard_offer = NULL;
static Offer *primary_offer = NULL;
static Offer *pending_offer = NULL;
static Offer *pending_primary_offer = NULL;
static Source *clipboard_source = NULL;
static Source *primary_source = NULL;
static Transfer *transfers = NULL;

static struct xkb_context *xkbcontext = NULL;
static struct xkb_keymap *xkbkeymap = NULL;
static struct xkb_state *xkbstate = NULL;
static struct xkb_compose_table *composetable = NULL;
static struct xkb_compose_state *composestate = NULL;
static int shift_idx = -1, lock_idx = -1, ctrl_idx = -1;
static int mod1_idx = -1, mod2_idx = -1, mod3_idx = -1;
static int mod4_idx = -1, mod5_idx = -1;

static int repeatfd = -1;
static xkb_keycode_t repeatkey = 0;
static int repeatrate = 25, repeatdelay = 600;

static int ime_enabled = 0;
static int ime_active = 0;
/* Printable key text is held back briefly so an input method can claim it. */
static char *pendingtext = NULL;
static size_t pendinglen = 0, pendingcap = 0;
static struct timespec pendinguntil;
static int ime_x = 0, ime_y = 0;

static uint32_t last_serial = 0;
static uint32_t pointer_serial = 0;
static int pointer_hidden = 0;
static int pointer_x = 0, pointer_y = 0;

static double scale = 1.0;
static int iscale = 1;
static int use_viewport = 0;
static int have_frac_scale = 0;
static uint32_t frac_scale = 120;
static int surface_scale = 0;
static int started = 0;
static int configured = 0;
static int draw_pending = 0;
static int pending_event = 0;
static int conf_w = 0, conf_h = 0;
static int shm_argb = 0;

static int clipx1, clipy1, clipx2, clipy2;

static uint buttonstate = 0;
static int axis_dy = 0, axis_dx = 0;
static double axis_y = 0, axis_x = 0;
static int oldbutton = 3; /* button event on startup: 3 = release */

static char *opt_alpha = NULL;
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_font = NULL;
static char *opt_io = NULL;
static char *opt_line = NULL;
static char *opt_title = NULL;
static int opt_fixed = 0;

/* Rendering */
static uint32_t premul(uint32_t);
static uint32_t invert(uint32_t);
static int parsecolor(const char *, uint32_t *);
static int loadcolor(int, const char *, uint32_t *);
static int sixd_to_16bit(int);
static void setclip(int, int, int, int);
static void resetclip(void);
static void drawglyphbitmap(FT_Face, uint32_t, int, int, uint32_t);
static FT_Face loadfallbackface(Font *, Rune, FT_UInt *);
static int makeglyphspecs(GlyphSpec *, const Glyph *, int, int, int);
static void drawglyphspecs(const GlyphSpec *, Glyph, int, int, int);
static void drawglyph(Glyph, int, int);

/* Fonts */
static int loadfont(Font *, FcPattern *, double);
static void loadfonts(char *, double);
static void unloadfont(Font *);
static void unloadfonts(void);

/* Window */
static int borderpxdev(void);
static double surfacescale(void);
static int topx(double);
static int tological(int);
static void updatesize(void);
static void destroybuffer(void);
static int createbuffer(void);
static void wlresize(void);
static void cresize(int, int);
static void setscale(double);
static void flushdisplay(void);
static uint32_t cursorshapeid(void);
static void setcursorshape(uint32_t);
static void hidepointer(uint32_t);
static void showpointer(uint32_t);

/* Input */
static uint modmask(void);
static void updatemods(void);
static void keypress(xkb_keycode_t);
static int keytext(xkb_keycode_t, char *, int);
static char *kmap(KeySym, uint);
static int match(uint, uint);
static void startrepeat(xkb_keycode_t);
static void stoprepeat(void);
static void repeattick(void);
static uint buttonmask(uint);
static int evcol(MouseEvent *);
static int evrow(MouseEvent *);
static void bpress(MouseEvent *);
static void brelease(MouseEvent *);
static void bmotion(MouseEvent *);
static void mousesel(MouseEvent *, int);
static void mousereport(MouseEvent *);
static int mouseaction(MouseEvent *, uint);

/* Clipboard */
static void offerdestroy(Offer *);
static void offeraddmime(Offer *, const char *);
static const char *offerpicked(Offer *);
static void offerreceive(Offer *, int);
static void pasteselection(Offer *);
static void sourcedestroy(Source *);
static void newsource(int, const char *);
static void transferdone(Transfer *);
static void transferadd(int, int, const char *, size_t);
static void transferready(Transfer *);
static void transferspoll(struct pollfd *);

/* IME */
static void pendingflush(void);
static void pendingdiscard(void);
static void textqueue(const char *, size_t);
static int hascontrol(const char *, int);
static void imeenable(void);
static void imedisable(void);
static void imespotsend(void);

/* Setup and main loop */
static void wlinit(int, int);
static void run(void);
static void usage(void);

static const struct wl_buffer_listener buffer_listener;
static const struct wl_data_offer_listener data_offer_listener;
static const struct zwp_primary_selection_offer_v1_listener
	primary_offer_listener;
static const struct wl_data_source_listener data_source_listener;
static const struct zwp_primary_selection_source_v1_listener
	primary_source_listener;
static const struct wl_keyboard_listener keyboard_listener;
static const struct wl_pointer_listener pointer_listener;
static const struct wl_shm_listener shm_listener;
static const struct wl_registry_listener registry_listener;

/* Listener callbacks */
static void registryglobal(void *, struct wl_registry *, uint32_t,
                           const char *, uint32_t);
static void registryglobalremove(void *, struct wl_registry *, uint32_t);
static void shmformat(void *, struct wl_shm *, uint32_t);
static void bufferrelease(void *, struct wl_buffer *);
static void wmbaseping(void *, struct xdg_wm_base *, uint32_t);
static void xdgsurfaceconfigure(void *, struct xdg_surface *, uint32_t);
static void toplevelconfigure(void *, struct xdg_toplevel *, int32_t,
                             int32_t, struct wl_array *);
static void toplevelclose(void *, struct xdg_toplevel *);
static void toplevelbounds(void *, struct xdg_toplevel *, int32_t, int32_t);
static void toplevelcaps(void *, struct xdg_toplevel *, struct wl_array *);
static void surfaceenter(void *, struct wl_surface *, struct wl_output *);
static void surfaceleave(void *, struct wl_surface *, struct wl_output *);
static void surfaceprefscale(void *, struct wl_surface *, int32_t);
static void surfacepreftransform(void *, struct wl_surface *, uint32_t);
static void fractionalprefscale(void *, struct wp_fractional_scale_v1 *,
                                uint32_t);
static void seatcapabilities(void *, struct wl_seat *, uint32_t);
static void seatname(void *, struct wl_seat *, const char *);
static void keyboardkeymap(void *, struct wl_keyboard *, uint32_t, int32_t,
                           uint32_t);
static void keyboardenter(void *, struct wl_keyboard *, uint32_t,
                          struct wl_surface *, struct wl_array *);
static void keyboardleave(void *, struct wl_keyboard *, uint32_t,
                          struct wl_surface *);
static void keyboardkey(void *, struct wl_keyboard *, uint32_t, uint32_t,
                        uint32_t, uint32_t);
static void keyboardmodifiers(void *, struct wl_keyboard *, uint32_t,
                              uint32_t, uint32_t, uint32_t, uint32_t);
static void keyboardrepeat(void *, struct wl_keyboard *, int32_t, int32_t);
static void pointerenter(void *, struct wl_pointer *, uint32_t,
                         struct wl_surface *, wl_fixed_t, wl_fixed_t);
static void pointerleave(void *, struct wl_pointer *, uint32_t,
                         struct wl_surface *);
static void pointermotion(void *, struct wl_pointer *, uint32_t, wl_fixed_t,
                          wl_fixed_t);
static void pointerbutton(void *, struct wl_pointer *, uint32_t, uint32_t,
                          uint32_t, uint32_t);
static void pointeraxis(void *, struct wl_pointer *, uint32_t, uint32_t,
                        wl_fixed_t);
static void pointerframe(void *, struct wl_pointer *);
static void pointeraxissource(void *, struct wl_pointer *, uint32_t);
static void pointeraxisstop(void *, struct wl_pointer *, uint32_t, uint32_t);
static void pointeraxisdisc(void *, struct wl_pointer *, uint32_t, int32_t);
static void dataoffer(void *, struct wl_data_device *,
                      struct wl_data_offer *);
static void dataenter(void *, struct wl_data_device *, uint32_t,
                      struct wl_surface *, wl_fixed_t, wl_fixed_t,
                      struct wl_data_offer *);
static void dataleave(void *, struct wl_data_device *);
static void datamotion(void *, struct wl_data_device *, uint32_t,
                       wl_fixed_t, wl_fixed_t);
static void datadrop(void *, struct wl_data_device *);
static void dataselection(void *, struct wl_data_device *,
                          struct wl_data_offer *);
static void primaryoffer(void *, struct zwp_primary_selection_device_v1 *,
                         struct zwp_primary_selection_offer_v1 *);
static void primaryselection(void *, struct zwp_primary_selection_device_v1 *,
                             struct zwp_primary_selection_offer_v1 *);
static void offeroffer(void *, struct wl_data_offer *, const char *);
static void primaryofferoffer(void *,
                              struct zwp_primary_selection_offer_v1 *,
                              const char *);
static void datasourcesend(void *, struct wl_data_source *, const char *,
                           int32_t);
static void datasourcecancelled(void *, struct wl_data_source *);
static void primarysourcesend(void *,
                              struct zwp_primary_selection_source_v1 *,
                              const char *, int32_t);
static void primarysourcecancelled(void *,
                                   struct zwp_primary_selection_source_v1 *);
static void textinputenter(void *, struct zwp_text_input_v3 *,
                           struct wl_surface *);
static void textinputleave(void *, struct zwp_text_input_v3 *,
                           struct wl_surface *);
static void textinputpreedit(void *, struct zwp_text_input_v3 *, const char *,
                             int32_t, int32_t);
static void textinputcommit(void *, struct zwp_text_input_v3 *, const char *);
static void textinputdelete(void *, struct zwp_text_input_v3 *, uint32_t,
                            uint32_t);
static void textinputdone(void *, struct zwp_text_input_v3 *, uint32_t);
static void textinputaction(void *, struct zwp_text_input_v3 *, uint32_t,
                            uint32_t);
static void textinputlanguage(void *, struct zwp_text_input_v3 *, const char *);
static void textinputpreedithint(void *, struct zwp_text_input_v3 *, uint32_t,
                                 uint32_t, uint32_t);

/* ---- colors ---- */

static int
sixd_to_16bit(int x)
{
	return x == 0 ? 0 : 0x3737 + 0x2828 * x;
}

static uint32_t
premul(uint32_t c)
{
	uint32_t a = c >> 24;
	uint32_t r = (c >> 16) & 0xff;
	uint32_t g = (c >> 8) & 0xff;
	uint32_t b = c & 0xff;

	if (a == 0xff)
		return c;
	if (a == 0)
		return 0;
	return (a << 24) | (((r * a + 127) / 255) << 16) |
	       (((g * a + 127) / 255) << 8) | ((b * a + 127) / 255);
}

static uint32_t
invert(uint32_t c)
{
	return (c & 0xff000000) | (~c & 0x00ffffff);
}

static int
hexval(int c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	c |= 0x20;
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	return -1;
}

static int
parsehex(const char **pp, int *digits)
{
	int v = 0, n = 0, c;

	while (n < 4 && (c = hexval((unsigned char)**pp)) >= 0) {
		v = v * 16 + c;
		(*pp)++;
		n++;
	}
	*digits = n;
	return v;
}

static int
parsenamed(const char *name, uint32_t *out)
{
	static const struct {
		const char *name;
		uint32_t rgb;
	} colors[] = {
		{ "black", 0x000000 }, { "white", 0xffffff },
		{ "red", 0xff0000 }, { "green", 0x00ff00 },
		{ "blue", 0x0000ff }, { "yellow", 0xffff00 },
		{ "magenta", 0xff00ff }, { "cyan", 0x00ffff },
		{ "gray", 0xbebebe }, { "grey", 0xbebebe },
		{ "darkgray", 0xa9a9a9 }, { "darkgrey", 0xa9a9a9 },
		{ "lightgray", 0xd3d3d3 }, { "lightgrey", 0xd3d3d3 },
		{ "orange", 0xffa500 }, { "purple", 0xa020f0 },
		{ "pink", 0xffc0cb }, { "brown", 0xa52a2a },
		{ "navy", 0x000080 }, { "teal", 0x008080 },
		{ "olive", 0x808000 }, { "maroon", 0x800000 },
		{ "silver", 0xc0c0c0 }, { "lime", 0x00ff00 },
		{ "aqua", 0x00ffff }, { "fuchsia", 0xff00ff },
	};
	size_t i;

	for (i = 0; i < LEN(colors); i++)
		if (!strcasecmp(name, colors[i].name)) {
			*out = 0xff000000 | colors[i].rgb;
			return 0;
		}
	return 1;
}

/* Accepts #rgb, #rrggbb, #rrggbbaa, rgb:r/g/b (1-4 hex digits each) and a
 * small set of names, so config.h can use whatever people are used to. */
static int
parsecolor(const char *name, uint32_t *out)
{
	const char *p;
	unsigned int r = 0, g = 0, b = 0, a = 0xff;
	int n, digits, i;

	if (name[0] == '#') {
		p = name + 1;
		n = strlen(p);
		if (n != 3 && n != 6 && n != 8)
			return 1;
		if (n == 3) {
			int hv;

			if ((hv = hexval(p[0])) < 0 ||
			    (r = hv * 0x11, (hv = hexval(p[1])) < 0) ||
			    (g = hv * 0x11, (hv = hexval(p[2])) < 0))
				return 1;
			b = hv * 0x11;
			*out = 0xff000000 | r << 16 | g << 8 | b;
			return 0;
		}
		for (i = 0; i < n; i += 2) {
			int hi = hexval(p[i]), lo = hexval(p[i + 1]);

			if (hi < 0 || lo < 0)
				return 1;
			if (i == 0)
				r = hi * 16 + lo;
			else if (i == 2)
				g = hi * 16 + lo;
			else if (i == 4)
				b = hi * 16 + lo;
			else
				a = hi * 16 + lo;
		}
		*out = a << 24 | r << 16 | g << 8 | b;
		return 0;
	}

	if (!strncmp(name, "rgb:", 4)) {
		unsigned int v[3];

		p = name + 4;
		for (i = 0; i < 3; i++) {
			if (i > 0 && *p++ != '/')
				return 1;
			v[i] = parsehex(&p, &digits);
			if (digits < 1)
				return 1;
			if (digits == 1)
				v[i] = v[i] * 0x11;
			else if (digits == 3)
				v[i] = (v[i] * 255 + 0x7ff) / 0xfff;
			else if (digits == 4)
				v[i] = v[i] >> 8;
		}
		*out = 0xff000000 | v[0] << 16 | v[1] << 8 | v[2];
		return 0;
	}

	return parsenamed(name, out);
}

static int
loadcolor(int i, const char *name, uint32_t *out)
{
	unsigned int r, g, b;

	if (!name) {
		if (BETWEEN(i, 16, 255)) {
			if (i < 6 * 6 * 6 + 16) {
				r = sixd_to_16bit(((i - 16) / 36) % 6) >> 8;
				g = sixd_to_16bit(((i - 16) / 6) % 6) >> 8;
				b = sixd_to_16bit(((i - 16) / 1) % 6) >> 8;
			} else {
				r = g = b = 0x08 + 0x0a * (i - (6 * 6 * 6 + 16));
			}
			*out = 0xff000000 | r << 16 | g << 8 | b;
			return 0;
		}
		if (i < 0 || i >= (int)LEN(colorname) || !colorname[i])
			return 1;
		name = colorname[i];
	}
	return parsecolor(name, out);
}

void
loadcols(void)
{
	int i;
	uint32_t c, a;

	if (!dc.col) {
		dc.collen = MAX(LEN(colorname), 256);
		dc.col = xmalloc(dc.collen * sizeof(uint32_t));
	}

	for (i = 0; i < (int)dc.collen; i++) {
		if (loadcolor(i, NULL, &c)) {
			if (i < (int)LEN(colorname) && colorname[i])
				die("could not load color '%s'\n", colorname[i]);
			die("could not load color %d\n", i);
		}
		dc.col[i] = c;
	}

	/* set alpha value of bg color */
	if (opt_alpha)
		alpha = strtof(opt_alpha, NULL);
	a = (uint32_t)(0xff * alpha + 0.5);
	dc.col[defaultbg] = (dc.col[defaultbg] & 0x00ffffff) | a << 24;

	for (i = 0; i < (int)dc.collen; i++)
		dc.col[i] = premul(dc.col[i]);
}

int
setcolorname(int x, const char *name)
{
	uint32_t c;

	if (x < 0 || x >= (int)dc.collen)
		return 1;

	if (loadcolor(x, name, &c))
		return 1;

	if (x == (int)defaultbg)
		c = (c & 0x00ffffff) | (uint32_t)(0xff * alpha + 0.5) << 24;
	dc.col[x] = premul(c);

	return 0;
}

/* ---- fonts ---- */

static int
loadfont(Font *f, FcPattern *pattern, double px)
{
	FcPattern *configured, *match;
	FcResult result;
	FT_Face face;
	FcChar8 *file;
	FcBool outline;
	int index, wantattr, haveattr;
	double size;
	unsigned int i, n = 0;
	long adv = 0;

	configured = FcPatternDuplicate(pattern);
	if (!configured)
		return 1;

	if (px > 0) {
		FcPatternDel(configured, FC_PIXEL_SIZE);
		FcPatternDel(configured, FC_SIZE);
		FcPatternAddDouble(configured, FC_PIXEL_SIZE, px);
	}

	FcConfigSubstitute(NULL, configured, FcMatchPattern);
	FcDefaultSubstitute(configured);

	match = FcFontMatch(NULL, configured, &result);
	if (!match) {
		FcPatternDestroy(configured);
		return 1;
	}

	if (px > 0) {
		f->pxsize = px;
		f->logicalpx = px / scale;
	} else {
		if (FcPatternGetDouble(match, FC_PIXEL_SIZE, 0, &size) !=
		    FcResultMatch)
			size = 12;
		f->logicalpx = size;
		f->pxsize = size * scale;
	}

	/* Only scalable outlines; bitmap emoji still come through fontconfig
	 * as scalable color fonts, but a stale bitmap family would be blurry. */
	if (FcPatternGetBool(match, FC_OUTLINE, 0, &outline) == FcResultMatch &&
	    !outline) {
		FcPatternDestroy(match);
		FcPatternDestroy(configured);
		return 1;
	}

	if (FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch ||
	    FcPatternGetInteger(match, FC_INDEX, 0, &index) != FcResultMatch) {
		FcPatternDestroy(match);
		FcPatternDestroy(configured);
		return 1;
	}

	if (FT_New_Face(ftlib, (const char *)file, index, &face)) {
		FcPatternDestroy(match);
		FcPatternDestroy(configured);
		return 1;
	}

	FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	FT_Set_Char_Size(face, 0, (FT_F26Dot6)(f->pxsize * 64 + 0.5), 72, 72);

	if (FcPatternGetInteger(pattern, FC_SLANT, 0, &wantattr) ==
	    FcResultMatch) {
		if (FcPatternGetInteger(match, FC_SLANT, 0, &haveattr) !=
		    FcResultMatch || haveattr < wantattr) {
			f->badslant = 1;
			fputs("font slant does not match\n", stderr);
		}
	}
	if (FcPatternGetInteger(pattern, FC_WEIGHT, 0, &wantattr) ==
	    FcResultMatch) {
		if (FcPatternGetInteger(match, FC_WEIGHT, 0, &haveattr) !=
		    FcResultMatch || haveattr != wantattr) {
			f->badweight = 1;
			fputs("font weight does not match\n", stderr);
		}
	}

	f->ascent = (face->size->metrics.ascender + 63) >> 6;
	f->descent = (-face->size->metrics.descender + 63) >> 6;
	f->height = f->ascent + f->descent;

	for (i = 0; ascii_printable[i]; i++) {
		if (FT_Load_Char(face, (unsigned char)ascii_printable[i],
		                 FT_LOAD_DEFAULT))
			continue;
		adv += face->glyph->advance.x;
		n++;
	}
	f->width = n ? (int)ceil((double)adv / (64.0 * n)) : 1;
	f->width = MAX(1, f->width);

	f->face = face;
	f->pattern = configured;

	FcPatternDestroy(match);
	return 0;
}

static void
loadfonts(char *fontstr, double fontsize)
{
	FcPattern *pattern;
	double px, fontval;

	pattern = FcNameParse((const FcChar8 *)fontstr);
	if (!pattern)
		die("can't open font %s\n", fontstr);

	if (fontsize > 1) {
		FcPatternDel(pattern, FC_PIXEL_SIZE);
		FcPatternDel(pattern, FC_SIZE);
		FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontsize);
		usedfontsize = fontsize;
		px = fontsize * scale;
	} else {
		px = 0;
		if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
		    FcResultMatch) {
			usedfontsize = fontval;
		} else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
		           FcResultMatch) {
			usedfontsize = -1;
		} else {
			FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
			usedfontsize = 12;
		}
		defaultfontsize = usedfontsize;
	}

	if (loadfont(&dc.font, pattern, px))
		die("can't open font %s\n", fontstr);

	if (usedfontsize < 0) {
		usedfontsize = dc.font.logicalpx;
		if (fontsize == 0)
			defaultfontsize = usedfontsize;
	}

	win.cw = MAX(1, (int)ceil(dc.font.width * cwscale));
	win.ch = MAX(1, (int)ceil(dc.font.height * chscale));

	FcPatternDel(pattern, FC_SLANT);
	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
	if (loadfont(&dc.ifont, pattern, dc.font.pxsize))
		die("can't open font %s\n", fontstr);

	FcPatternDel(pattern, FC_WEIGHT);
	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
	if (loadfont(&dc.ibfont, pattern, dc.font.pxsize))
		die("can't open font %s\n", fontstr);

	FcPatternDel(pattern, FC_SLANT);
	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
	if (loadfont(&dc.bfont, pattern, dc.font.pxsize))
		die("can't open font %s\n", fontstr);

	fontsloaded = 1;
	FcPatternDestroy(pattern);
}

static void
unloadfont(Font *f)
{
	if (f->face)
		FT_Done_Face(f->face);
	if (f->pattern)
		FcPatternDestroy(f->pattern);
	memset(f, 0, sizeof(*f));
}

static void
unloadfonts(void)
{
	int i;

	/* HarfBuzz keeps pointers to the FT_Face objects. */
	hbunloadfonts();

	for (i = 0; i < frclen; i++)
		FT_Done_Face(frc[i].face);
	free(frc);
	frc = NULL;
	frclen = frccap = 0;

	unloadfont(&dc.font);
	unloadfont(&dc.bfont);
	unloadfont(&dc.ifont);
	unloadfont(&dc.ibfont);

	fontsloaded = 0;
}

/* ---- rendering ---- */

static void
setclip(int x, int y, int w, int h)
{
	clipx1 = MAX(0, x);
	clipy1 = MAX(0, y);
	clipx2 = MIN(win.w, x + w);
	clipy2 = MIN(win.h, y + h);
}

static void
resetclip(void)
{
	clipx1 = 0;
	clipy1 = 0;
	clipx2 = win.w;
	clipy2 = win.h;
}

void
clearrect(int x, int y, int w, int h, uint32_t col)
{
	uint32_t *p;
	int x1, y1, x2, y2, i, j;

	if (!fb.target)
		return;

	x1 = MAX(x, clipx1);
	y1 = MAX(y, clipy1);
	x2 = MIN(x + w, clipx2);
	y2 = MIN(y + h, clipy2);
	if (x1 >= x2 || y1 >= y2)
		return;

	fb.drawn = 1;
	for (j = y1; j < y2; j++) {
		p = fb.target + (size_t)j * win.w;
		for (i = x1; i < x2; i++)
			p[i] = col;
	}
}

void
blendrect(int x, int y, int w, int h, uint32_t col)
{
	uint32_t *p, d, sr, sg, sb, sa, ia;
	int x1, y1, x2, y2, i, j;

	sa = col >> 24;
	if (!fb.target || sa == 0)
		return;

	x1 = MAX(x, clipx1);
	y1 = MAX(y, clipy1);
	x2 = MIN(x + w, clipx2);
	y2 = MIN(y + h, clipy2);
	if (x1 >= x2 || y1 >= y2)
		return;

	sr = (col >> 16) & 0xff;
	sg = (col >> 8) & 0xff;
	sb = col & 0xff;
	ia = 255 - sa;

	fb.drawn = 1;
	for (j = y1; j < y2; j++) {
		p = fb.target + (size_t)j * win.w;
		for (i = x1; i < x2; i++) {
			d = p[i];
			p[i] = ((sa + (((d >> 24) * ia + 127) / 255)) << 24) |
			       ((sr + ((((d >> 16) & 0xff) * ia + 127) / 255)) << 16) |
			       ((sg + ((((d >> 8) & 0xff) * ia + 127) / 255)) << 8) |
			       (sb + (((d & 0xff) * ia + 127) / 255));
		}
	}
}

/* Rasterize one FreeType glyph at pen position (x, y). */
static void
drawglyphbitmap(FT_Face face, uint32_t glyph, int x, int y, uint32_t fg)
{
	FT_GlyphSlot slot;
	unsigned char *src, *row;
	uint32_t *p, d, cov, sa, ia, fr, fg_, fb_, sr, sg, sb;
	int i, j, w, h, stride, px, py, mode;

	if (!fb.target || !glyph)
		return;
	if (FT_Load_Glyph(face, glyph, FT_LOAD_RENDER | FT_LOAD_COLOR))
		return;
	slot = face->glyph;
	if (slot->format != FT_GLYPH_FORMAT_BITMAP) {
		if (FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL))
			return;
	}
	if (!slot->bitmap.width || !slot->bitmap.rows ||
	    !slot->bitmap.buffer)
		return;

	w = slot->bitmap.width;
	h = slot->bitmap.rows;
	stride = slot->bitmap.pitch;
	mode = slot->bitmap.pixel_mode;
	src = slot->bitmap.buffer;
	px = x + slot->bitmap_left;
	py = y - slot->bitmap_top;

	fr = (fg >> 16) & 0xff;
	fg_ = (fg >> 8) & 0xff;
	fb_ = fg & 0xff;
	ia = 0;

	for (j = 0; j < h; j++) {
		int yy = py + j;

		if (yy < clipy1 || yy >= clipy2)
			continue;
		row = stride >= 0 ? src + j * stride : src + (h - 1 - j) * -stride;
		p = fb.target + (size_t)yy * win.w;

		for (i = 0; i < w; i++) {
			int xx = px + i;

			if (xx < clipx1 || xx >= clipx2)
				continue;

			if (mode == FT_PIXEL_MODE_BGRA)
				cov = row[i * 4 + 3];
			else if (mode == FT_PIXEL_MODE_MONO)
				cov = (row[i >> 3] & (0x80 >> (i & 7))) ? 255 : 0;
			else
				cov = row[i];
			if (!cov)
				continue;

			sa = (fg >> 24) * cov / 255;
			if (!sa)
				continue;
			ia = 255 - sa;
			d = p[xx];

			if (mode == FT_PIXEL_MODE_BGRA) {
				/* FreeType color bitmaps are premultiplied BGRA */
				sr = row[i * 4 + 2] * (fg >> 24) / 255;
				sg = row[i * 4 + 1] * (fg >> 24) / 255;
				sb = row[i * 4 + 0] * (fg >> 24) / 255;
			} else {
				sr = fr * cov / 255;
				sg = fg_ * cov / 255;
				sb = fb_ * cov / 255;
			}

			p[xx] = ((sa + (((d >> 24) * ia + 127) / 255)) << 24) |
			        ((sr + ((((d >> 16) & 0xff) * ia + 127) / 255)) << 16) |
			        ((sg + ((((d >> 8) & 0xff) * ia + 127) / 255)) << 8) |
			        (sb + (((d & 0xff) * ia + 127) / 255));
			fb.drawn = 1;
		}
	}
}

/* Font Ring Cache */
enum {
	FRC_NORMAL,
	FRC_ITALIC,
	FRC_BOLD,
	FRC_ITALICBOLD
};

/* Face for a rune the configured font does not carry, or NULL when nothing on
 * the system does.  *glyphidx is its index for that face (0 when not found).
 *
 * fontconfig's cached charsets over-report: the best match it offers may have
 * no cmap entry at all for the rune, which is how CJK came out blank.  Only
 * FreeType knows what a loaded face really contains, so walk the sorted
 * candidates and ask it.  Capped because the set is the whole font cache and a
 * rune nobody covers must not turn a redraw into a file scan. */
static FT_Face
loadfallbackface(Font *f, Rune rune, FT_UInt *glyphidx)
{
	FcPattern *pattern;
	FcCharSet *charset;
	FcFontSet *set;
	FcResult result;
	FcChar8 *file;
	FT_Face face = NULL;
	int i, index;

	charset = FcCharSetCreate();
	pattern = FcPatternDuplicate(f->pattern);
	if (!charset || !pattern)
		die("out of memory looking up a fallback font\n");

	FcCharSetAddChar(charset, rune);
	FcPatternAddCharSet(pattern, FC_CHARSET, charset);
	FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
	FcConfigSubstitute(NULL, pattern, FcMatchPattern);
	FcDefaultSubstitute(pattern);

	*glyphidx = 0;
	set = FcFontSort(NULL, pattern, FcTrue, NULL, &result);
	for (i = 0; set && i < set->nfont && i < 32; i++) {
		if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) !=
		    FcResultMatch ||
		    FcPatternGetInteger(set->fonts[i], FC_INDEX, 0, &index) !=
		    FcResultMatch)
			continue;
		if (FT_New_Face(ftlib, (const char *)file, index, &face))
			continue;

		FT_Select_Charmap(face, FT_ENCODING_UNICODE);
		FT_Set_Char_Size(face, 0, (FT_F26Dot6)(f->pxsize * 64 + 0.5),
		                 72, 72);
		*glyphidx = FT_Get_Char_Index(face, rune);
		if (*glyphidx)
			break;
		FT_Done_Face(face);
	}

	if (set)
		FcFontSetDestroy(set);
	FcPatternDestroy(pattern);
	FcCharSetDestroy(charset);

	if (!*glyphidx)
		return NULL;
	return face;
}

/* Turn a run of glyphs into font specs.  Exactly one spec is appended per
 * non-dummy glyph so that hbtransform() can stay index-aligned. */
static int
makeglyphspecs(GlyphSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
	double winx = win.hborderpx + x * win.cw,
	       winy = win.vborderpx + y * win.ch, xp, yp;
	ushort mode, prevmode = USHRT_MAX;
	Font *font = &dc.font;
	int frcflags = FRC_NORMAL;
	double runewidth = win.cw;
	Rune rune;
	FT_UInt glyphidx;
	FT_Face face;
	int f, numspecs = 0, i;

	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
		rune = glyphs[i].u;
		mode = glyphs[i].mode;

		/* Skip dummy wide-character spacing. */
		if (mode & ATTR_WDUMMY)
			continue;

		/* Determine font for glyph if different from previous glyph. */
		if (prevmode != mode) {
			prevmode = mode;
			font = &dc.font;
			frcflags = FRC_NORMAL;
			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0 : 1.0);
			if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
				font = &dc.ibfont;
				frcflags = FRC_ITALICBOLD;
			} else if (mode & ATTR_ITALIC) {
				font = &dc.ifont;
				frcflags = FRC_ITALIC;
			} else if (mode & ATTR_BOLD) {
				font = &dc.bfont;
				frcflags = FRC_BOLD;
			}
			yp = winy + font->ascent;
		}

		if (mode & ATTR_BOXDRAW) {
			/* minor shoehorning: boxdraw uses only this ushort */
			glyphidx = boxdrawindex(&glyphs[i]);
		} else {
			glyphidx = FT_Get_Char_Index(font->face, rune);
		}
		if (glyphidx) {
			specs[numspecs].font = font->face;
			specs[numspecs].glyph = glyphidx;
			specs[numspecs].x = (short)xp;
			specs[numspecs].y = (short)yp;
			xp += runewidth;
			numspecs++;
			continue;
		}

		/* Fallback on font cache, search the font cache for match.  A NULL
		 * face is a rune nothing on the system covers, see below. */
		for (f = 0; f < frclen; f++) {
			glyphidx = frc[f].face ?
			           FT_Get_Char_Index(frc[f].face, rune) : 0;
			/* Everything correct. */
			if (glyphidx && frc[f].flags == frcflags)
				break;
			/* We got a default font for a not found glyph. */
			if (!glyphidx && frc[f].flags == frcflags &&
			    frc[f].unicodep == rune)
				break;
		}

		/* Nothing was found.  Ask fontconfig for a face carrying the rune
		 * and cache the answer either way, so a rune is looked up once and
		 * an uncovered one does not rescan the fonts on every redraw. */
		if (f >= frclen) {
			face = loadfallbackface(font, rune, &glyphidx);

			if (frclen >= frccap) {
				frccap += 16;
				frc = xrealloc(frc, frccap * sizeof(Fontcache));
			}
			frc[frclen].face = face;
			frc[frclen].flags = frcflags;
			frc[frclen].unicodep = rune;
			f = frclen++;
		}

		specs[numspecs].font = frc[f].face ? frc[f].face : font->face;
		specs[numspecs].glyph = glyphidx;
		specs[numspecs].x = (short)xp;
		specs[numspecs].y = (short)yp;
		xp += runewidth;
		numspecs++;
	}

	/* Harfbuzz transformation for ligatures. */
	hbtransform(specs, glyphs, len, x, y);

	return numspecs;
}

static void
drawglyph(Glyph g, int x, int y)
{
	int numspecs;
	GlyphSpec spec;

	numspecs = makeglyphspecs(&spec, &g, 1, x, y);
	drawglyphspecs(&spec, g, numspecs, x, y);
}

static void
drawglyphspecs(const GlyphSpec *specs, Glyph base, int len, int x, int y)
{
	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
	int winx = win.hborderpx + x * win.cw;
	int winy = win.vborderpx + y * win.ch;
	int width = charlen * win.cw;
	uint32_t fg, bg, temp;
	int i;

	/* Fallback on color display for attributes not supported by the font */
	if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
		if (dc.ibfont.badslant || dc.ibfont.badweight)
			base.fg = defaultattr;
	} else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
	           (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
		base.fg = defaultattr;
	}

	fg = IS_TRUECOL(base.fg) ? 0xff000000 | (base.fg & 0xffffff)
	                         : dc.col[base.fg];
	bg = IS_TRUECOL(base.bg) ? 0xff000000 | (base.bg & 0xffffff)
	                         : dc.col[base.bg];

	/* Change basic system colors [0-7] to bright system colors [8-15] */
	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
		fg = dc.col[base.fg + 8];

	if (IS_SET(MODE_REVERSE)) {
		if (!IS_TRUECOL(base.fg) && base.fg == defaultfg)
			fg = dc.col[defaultbg];
		else
			fg = invert(fg);

		if (!IS_TRUECOL(base.bg) && base.bg == defaultbg)
			bg = dc.col[defaultfg];
		else
			bg = invert(bg);
	}

	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT)
		fg = (fg & 0xff000000) | ((fg >> 1) & 0x7f7f7f);

	if (base.mode & ATTR_REVERSE) {
		temp = fg;
		fg = bg;
		bg = temp;
	}

	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
		fg = bg;

	if (base.mode & ATTR_INVISIBLE)
		fg = bg;

	/* Intelligent cleaning up of the borders, horizontally only: the top and
	 * the bottom padding always belong to the default background, see
	 * startdraw(), so that a coloured block on the first or the last row does
	 * not bleed out of its own row. */
	if (x == 0)
		clearrect(0, winy, win.hborderpx, win.ch, bg);
	if (winx + width >= win.hborderpx + win.tw)
		clearrect(winx + width, winy, win.w - (winx + width),
		          win.ch, bg);

	/* Clean up the region we want to draw to. */
	clearrect(winx, winy, width, win.ch, bg);

	/* Set the clip region because our primitives are dumb. */
	setclip(winx, winy, width, win.ch);

	if (base.mode & ATTR_BOXDRAW) {
		drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
	} else {
		for (i = 0; i < len; i++)
			drawglyphbitmap((FT_Face)specs[i].font, specs[i].glyph,
			                specs[i].x, specs[i].y, fg);
	}

	/* Render underline and strikethrough. */
	if (base.mode & ATTR_UNDERLINE)
		blendrect(winx, winy + dc.font.ascent + 1, width, 1, fg);

	if (base.mode & ATTR_STRUCK)
		blendrect(winx, winy + 2 * dc.font.ascent / 3, width, 1, fg);

	resetclip();
}

void
drawline(Line line, int x1, int y1, int x2)
{
	int i, x, ox, numspecs;
	Glyph base, new;
	GlyphSpec *specs = specbuf;

	if (!specs || !fb.target)
		return;

	numspecs = makeglyphspecs(specs, &line[x1], x2 - x1, x1, y1);
	i = ox = 0;
	for (x = x1; x < x2 && i < numspecs; x++) {
		new = line[x];
		if (new.mode == ATTR_WDUMMY)
			continue;
		if (selected(x, y1))
			new.mode ^= ATTR_REVERSE;
		if (i > 0 && ATTRCMP(base, new)) {
			drawglyphspecs(specs, base, i, ox, y1);
			specs += i;
			numspecs -= i;
			i = 0;
		}
		if (i == 0) {
			ox = x;
			base = new;
		}
		i++;
	}
	if (i > 0)
		drawglyphspecs(specs, base, i, ox, y1);
}

void
drawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line,
           int len)
{
	uint32_t drawcol;
	int ct;

	/* remove the old cursor */
	if (selected(ox, oy))
		og.mode ^= ATTR_REVERSE;

	/* Redraw the line where cursor was previously.
	 * It will restore the ligatures broken by the cursor. */
	drawline(line, 0, oy, len);

	if (IS_SET(MODE_HIDE))
		return;

	/*
	 * Select the right color for the right mode.
	 */
	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|
	          ATTR_BOXDRAW;

	if (IS_SET(MODE_REVERSE)) {
		g.mode |= ATTR_REVERSE;
		g.bg = defaultfg;
		if (selected(cx, cy)) {
			drawcol = dc.col[defaultcs];
			g.fg = defaultrcs;
		} else {
			drawcol = dc.col[defaultrcs];
			g.fg = defaultcs;
		}
	} else {
		if (selected(cx, cy)) {
			g.fg = defaultfg;
			g.bg = defaultrcs;
		} else if (!(og.mode & ATTR_REVERSE)) {
			uint32_t col = g.bg;

			g.bg = g.fg;
			g.fg = col;
		}

		if (IS_TRUECOL(g.bg))
			drawcol = 0xff000000 | (g.bg & 0xffffff);
		else
			drawcol = dc.col[g.bg];
	}

	ct = MAX(1, (int)(cursorthickness * scale + 0.5));

	/* draw the new one */
	if (IS_SET(MODE_FOCUSED)) {
		switch (win.cursor) {
		case 7: /* st extension */
			g.u = 0x2603; /* snowman (U+2603) */
			/* FALLTHROUGH */
		case 0: /* Blinking Block */
		case 1: /* Blinking Block (Default) */
		case 2: /* Steady Block */
			drawglyph(g, cx, cy);
			break;
		case 3: /* Blinking Underline */
		case 4: /* Steady Underline */
			blendrect(win.hborderpx + cx * win.cw,
			          win.vborderpx + (cy + 1) * win.ch - ct,
			          win.cw, ct, drawcol);
			break;
		case 5: /* Blinking bar */
		case 6: /* Steady bar */
			blendrect(win.hborderpx + cx * win.cw,
			          win.vborderpx + cy * win.ch, ct, win.ch,
			          drawcol);
			break;
		}
	} else {
		blendrect(win.hborderpx + cx * win.cw,
		          win.vborderpx + cy * win.ch, win.cw - 1, 1, drawcol);
		blendrect(win.hborderpx + cx * win.cw,
		          win.vborderpx + cy * win.ch, 1, win.ch - 1, drawcol);
		blendrect(win.hborderpx + (cx + 1) * win.cw - 1,
		          win.vborderpx + cy * win.ch, 1, win.ch - 1, drawcol);
		blendrect(win.hborderpx + cx * win.cw,
		          win.vborderpx + (cy + 1) * win.ch - 1, win.cw, 1,
		          drawcol);
	}
}

/* ---- shm buffer / drawing ---- */

int
startdraw(void)
{
	uint32_t padcol;

	if (!fb.data[0] || !IS_SET(MODE_VISIBLE))
		return 0;

	if (fb.busy[fb.cur]) {
		draw_pending = 1;
		return 0;
	}

	/* We commit whole buffers, so every frame repaints the whole screen.
	 * This also keeps the two rotating buffers identical. */
	fb.target = fb.data[fb.cur];
	fb.drawn = 0;
	draw_pending = 0;
	resetclip();

	/* The padding the character grid does not fill keeps the default
	 * background, even under reverse video.  Rows must not paint into it,
	 * a coloured first or last row would otherwise look too tall. */
	padcol = dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg];
	clearrect(0, 0, win.w, win.vborderpx, padcol);
	clearrect(0, win.vborderpx + win.th, win.w,
	          win.h - (win.vborderpx + win.th), padcol);
	tfulldirt();
	return 1;
}

void
finishdraw(void)
{
	if (!fb.target || !fb.drawn) {
		fb.drawn = 0;
		return;
	}

	wl_surface_attach(surface, fb.buffer[fb.cur], 0, 0);
	if (wl_surface_get_version(surface) >=
	    WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)
		wl_surface_damage_buffer(surface, 0, 0, win.w, win.h);
	else
		wl_surface_damage(surface, 0, 0, win.lw, win.lh);
	wl_surface_commit(surface);

	fb.busy[fb.cur] = 1;
	fb.cur ^= 1;
	fb.drawn = 0;
}

static void
bufferrelease(void *data, struct wl_buffer *buffer)
{
	int i = (int)(intptr_t)data;

	fb.busy[i] = 0;
	if (draw_pending) {
		draw_pending = 0;
		pending_event = 1;
	}
}

static int
createshm(size_t size)
{
	int fd;
	char name[32];

	fd = memfd_create("st", MFD_CLOEXEC);
	if (fd < 0) {
		snprintf(name, sizeof(name), "/st-%ld", (long)getpid());
		fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
		if (fd >= 0)
			shm_unlink(name);
	}
	if (fd < 0)
		return -1;
	if (ftruncate(fd, size) < 0) {
		close(fd);
		return -1;
	}
	return fd;
}

static void
destroybuffer(void)
{
	int i;

	for (i = 0; i < 2; i++) {
		if (fb.buffer[i])
			wl_buffer_destroy(fb.buffer[i]);
		if (fb.data[i])
			munmap(fb.data[i], fb.size);
		fb.buffer[i] = NULL;
		fb.data[i] = NULL;
		fb.busy[i] = 0;
	}
	fb.target = NULL;
	fb.size = 0;
	fb.stride = 0;
	fb.cur = 0;
	fb.drawn = 0;
	draw_pending = 0;
}

static int
createbuffer(void)
{
	struct wl_shm_pool *pool;
	int fd, stride, i;

	if (win.w < 1 || win.h < 1)
		return 1;

	stride = win.w * 4;
	fb.size = (size_t)stride * win.h;

	for (i = 0; i < 2; i++) {
		fd = createshm(fb.size);
		if (fd < 0)
			goto fail;

		fb.data[i] = mmap(NULL, fb.size, PROT_READ | PROT_WRITE,
		                  MAP_SHARED, fd, 0);
		if (fb.data[i] == MAP_FAILED) {
			fb.data[i] = NULL;
			close(fd);
			goto fail;
		}

		pool = wl_shm_create_pool(shm, fd, fb.size);
		fb.buffer[i] = wl_shm_pool_create_buffer(pool, 0, win.w, win.h,
		                                         stride,
		                                         WL_SHM_FORMAT_ARGB8888);
		wl_shm_pool_destroy(pool);
		close(fd);

		if (!fb.buffer[i])
			goto fail;
		fb.busy[i] = 0;
	}

	fb.stride = stride;
	fb.cur = 0;
	fb.target = fb.data[0];
	fb.drawn = 0;
	for (i = 0; i < 2; i++)
		wl_buffer_add_listener(fb.buffer[i], &buffer_listener,
		                       (void *)(intptr_t)i);
	return 0;

fail:
	destroybuffer();
	return 1;
}

static void
wlresize(void)
{
	if (createbuffer())
		die("failed to create wl_shm buffer\n");
}

/* ---- window geometry and scaling ---- */

static int
borderpxdev(void)
{
	return borderpx ? MAX(1, (int)ceil(borderpx * scale)) : 0;
}

static double
surfacescale(void)
{
	return use_viewport ? scale : iscale;
}

static int
topx(double v)
{
	return (int)(v * surfacescale() + 0.5);
}

static int
tological(int v)
{
	return (int)(v / surfacescale() + 0.5);
}

static void
updatesize(void)
{
	if (use_viewport) {
		win.w = (int)ceil(win.lw * scale);
		win.h = (int)ceil(win.lh * scale);
		if (surface)
			wl_surface_set_buffer_scale(surface, 1);
		if (viewport && win.lw > 0 && win.lh > 0)
			wp_viewport_set_destination(viewport, win.lw, win.lh);
	} else {
		iscale = MAX(1, (int)ceil(scale));
		win.w = win.lw * iscale;
		win.h = win.lh * iscale;
		if (surface)
			wl_surface_set_buffer_scale(surface, iscale);
	}
	win.w = MAX(1, win.w);
	win.h = MAX(1, win.h);
}

static void
setscale(double s)
{
	int oldviewport = use_viewport;
	int changed;

	if (s <= 0)
		return;

	changed = fabs(s - scale) > 1e-6;
	scale = s;
	use_viewport = have_frac_scale && viewporter != NULL;
	changed = changed || use_viewport != oldviewport;
	updatesize();

	if (!changed || !fontsloaded)
		return;

	unloadfonts();
	loadfonts(usedfont, usedfontsize);
	if (started)
		cresize(win.lw, win.lh);
}

static void
cresize(int lw, int lh)
{
	int col, row, bw;

	if (lw > 0)
		win.lw = lw;
	if (lh > 0)
		win.lh = lh;

	updatesize();
	bw = borderpxdev();

	col = (win.w - 2 * bw) / win.cw;
	row = (win.h - 2 * bw) / win.ch;
	col = MAX(1, col);
	row = MAX(1, row);

	win.hborderpx = (win.w - col * win.cw) / 2;
	win.vborderpx = (win.h - row * win.ch) / 2;
	win.tw = col * win.cw;
	win.th = row * win.ch;

	/* Make sure anything drawn from tresize() (keyboard_select) cannot
	 * touch a buffer the compositor is still reading. */
	specbuf = xrealloc(specbuf, col * sizeof(GlyphSpec));
	destroybuffer();
	tresize(col, row);
	wlresize();
	win.mode |= MODE_VISIBLE;

	if (started)
		ttyresize(win.tw, win.th);
	if (opt_fixed) {
		xdg_toplevel_set_min_size(xdg_toplevel, win.lw, win.lh);
		xdg_toplevel_set_max_size(xdg_toplevel, win.lw, win.lh);
	}
}

/* ---- xdg-shell / surface callbacks ---- */

static void
wmbaseping(void *data, struct xdg_wm_base *base, uint32_t serial)
{
	xdg_wm_base_pong(base, serial);
}

static void
xdgsurfaceconfigure(void *data, struct xdg_surface *s, uint32_t serial)
{
	xdg_surface_ack_configure(s, serial);

	if (conf_w > 0 && conf_h > 0 &&
	    (conf_w != win.lw || conf_h != win.lh)) {
		win.lw = conf_w;
		win.lh = conf_h;
		if (started)
			cresize(win.lw, win.lh);
	}
	configured = 1;
}

static void
toplevelconfigure(void *data, struct xdg_toplevel *t, int32_t w, int32_t h,
                  struct wl_array *states)
{
	conf_w = w;
	conf_h = h;
}

static void
toplevelclose(void *data, struct xdg_toplevel *t)
{
	ttyhangup();
	exit(0);
}

static void
toplevelbounds(void *data, struct xdg_toplevel *t, int32_t w, int32_t h)
{
	/* no size hints on Wayland; the grid is centered instead */
}

static void
toplevelcaps(void *data, struct xdg_toplevel *t, struct wl_array *caps)
{
}

static void
surfaceenter(void *data, struct wl_surface *s, struct wl_output *o)
{
}

static void
surfaceleave(void *data, struct wl_surface *s, struct wl_output *o)
{
}

static void
surfaceprefscale(void *data, struct wl_surface *s, int32_t factor)
{
	/* The integer fallback is only used without fractional-scale. */
	if (fractional_manager)
		return;
	surface_scale = factor;
	setscale(factor);
}

static void
surfacepreftransform(void *data, struct wl_surface *s, uint32_t transform)
{
}

static void
fractionalprefscale(void *data, struct wp_fractional_scale_v1 *fs,
                    uint32_t s)
{
	if (s == 0)
		return;
	frac_scale = s;
	have_frac_scale = 1;
	setscale(s / 120.0);
}

static const struct wl_buffer_listener buffer_listener = {
	.release = bufferrelease,
};

static const struct xdg_wm_base_listener wm_base_listener = {
	.ping = wmbaseping,
};

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

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
	.configure = toplevelconfigure,
	.close = toplevelclose,
	.configure_bounds = toplevelbounds,
	.wm_capabilities = toplevelcaps,
};

static const struct wl_surface_listener surface_listener = {
	.enter = surfaceenter,
	.leave = surfaceleave,
	.preferred_buffer_scale = surfaceprefscale,
	.preferred_buffer_transform = surfacepreftransform,
};

static const struct wp_fractional_scale_v1_listener fractional_listener = {
	.preferred_scale = fractionalprefscale,
};

/* ---- clipboard and primary selection ---- */

static const char *mimetypes[] = {
	"text/plain;charset=utf-8",
	"UTF8_STRING",
	"text/plain",
	"STRING",
	"TEXT",
};

static void
offeraddmime(Offer *o, const char *mime)
{
	o->mimes = xrealloc(o->mimes, (o->nmimes + 1) * sizeof(char *));
	o->mimes[o->nmimes++] = xstrdup((char *)mime);
}

static void
offerdestroy(Offer *o)
{
	int i;

	if (!o)
		return;
	if (o->data)
		wl_data_offer_destroy(o->data);
	if (o->primary)
		zwp_primary_selection_offer_v1_destroy(o->primary);
	for (i = 0; i < o->nmimes; i++)
		free(o->mimes[i]);
	free(o->mimes);
	free(o);
}

static const char *
offerpicked(Offer *o)
{
	size_t i;
	int j;

	if (!o)
		return NULL;

	for (i = 0; i < LEN(mimetypes); i++)
		for (j = 0; j < o->nmimes; j++)
			if (!strcmp(o->mimes[j], mimetypes[i]))
				return mimetypes[i];
	return NULL;
}

static void
offerreceive(Offer *o, int fd)
{
	const char *mime = offerpicked(o);

	if (!mime) {
		fprintf(stderr, "no usable selection mime type\n");
		close(fd);
		return;
	}
	if (o->data)
		wl_data_offer_receive(o->data, mime, fd);
	else
		zwp_primary_selection_offer_v1_receive(o->primary, mime, fd);
	close(fd);
}

static void
transferdone(Transfer *t)
{
	Transfer **pp;

	for (pp = &transfers; *pp; pp = &(*pp)->next)
		if (*pp == t) {
			*pp = t->next;
			break;
		}
	close(t->fd);
	free(t->buf);
	free(t);
}

static void
transferready(Transfer *t)
{
	char buf[8192];
	char *p;
	ssize_t n;

	if (t->writing) {
		while (t->off < t->len) {
			n = write(t->fd, t->buf + t->off, t->len - t->off);
			if (n < 0) {
				if (errno == EINTR)
					continue;
				if (errno == EAGAIN)
					return;
				transferdone(t);
				return;
			}
			t->off += n;
		}
		transferdone(t);
		return;
	}

	for (;;) {
		n = read(t->fd, buf, sizeof(buf));
		if (n < 0) {
			if (errno == EINTR)
				continue;
			if (errno == EAGAIN)
				return;
			transferdone(t);
			return;
		}
		if (n == 0) {
			if (t->len) {
				t->buf = xrealloc(t->buf, t->len + 1);
				t->buf[t->len] = '\0';
				for (p = t->buf; *p; p++)
					if (*p == '\n')
						*p = '\r';
				if (IS_SET(MODE_BRCKTPASTE))
					ttywrite("\033[200~", 6, 0);
				ttywrite(t->buf, t->len, 1);
				if (IS_SET(MODE_BRCKTPASTE))
					ttywrite("\033[201~", 6, 0);
			}
			transferdone(t);
			return;
		}
		if (t->len + n > t->cap) {
			t->cap = MAX(8192, t->len + n);
			t->buf = xrealloc(t->buf, t->cap);
		}
		memcpy(t->buf + t->len, buf, n);
		t->len += n;
	}
}

static void
transferadd(int fd, int writing, const char *buf, size_t len)
{
	Transfer *t;

	t = xmalloc(sizeof(*t));
	memset(t, 0, sizeof(*t));
	t->fd = fd;
	t->writing = writing;
	if (writing) {
		t->buf = xstrdup((char *)buf);
		t->len = len;
	}
	t->next = transfers;
	transfers = t;

	if (writing)
		transferready(t);
}

static void
transferspoll(struct pollfd *pfd)
{
	Transfer *t, *next;
	int i = 0;

	for (t = transfers; t; t = next, i++) {
		next = t->next;
		if (pfd[i].revents)
			transferready(t);
	}
}

static void
pasteselection(Offer *o)
{
	int fds[2];

	if (!o)
		return;

	if (pipe2(fds, O_CLOEXEC) < 0) {
		fprintf(stderr, "pipe2: %s\n", strerror(errno));
		return;
	}
	fcntl(fds[0], F_SETFL, O_NONBLOCK);
	offerreceive(o, fds[1]);
	transferadd(fds[0], 0, NULL, 0);
}

static void
sourcedestroy(Source *s)
{
	if (!s)
		return;
	if (s->data) {
		if (clipboard_source == s)
			clipboard_source = NULL;
		wl_data_source_destroy(s->data);
	}
	if (s->primary) {
		if (primary_source == s)
			primary_source = NULL;
		zwp_primary_selection_source_v1_destroy(s->primary);
	}
	free(s->text);
	free(s);
}

static void
newsource(int primary, const char *text)
{
	Source *s;
	size_t i;

	s = xmalloc(sizeof(*s));
	memset(s, 0, sizeof(*s));
	s->text = xstrdup((char *)text);

	if (primary && primary_manager && primary_device) {
		s->primary =
			zwp_primary_selection_device_manager_v1_create_source(
				primary_manager);
		zwp_primary_selection_source_v1_add_listener(
			s->primary, &primary_source_listener, s);
		for (i = 0; i < LEN(mimetypes); i++)
			zwp_primary_selection_source_v1_offer(s->primary,
			                                      mimetypes[i]);
		primary_source = s;
		zwp_primary_selection_device_v1_set_selection(
			primary_device, s->primary, last_serial);
	} else if (!primary && data_manager && data_device) {
		s->data = wl_data_device_manager_create_data_source(data_manager);
		wl_data_source_add_listener(s->data, &data_source_listener, s);
		for (i = 0; i < LEN(mimetypes); i++)
			wl_data_source_offer(s->data, mimetypes[i]);
		clipboard_source = s;
		wl_data_device_set_selection(data_device, s->data, last_serial);
	} else {
		free(s->text);
		free(s);
	}
}

void
setsel(char *str)
{
	if (!str)
		return;

	free(xsel.primary);
	xsel.primary = str;

	newsource(1, xsel.primary);
	newsource(0, xsel.primary);
}

void
clipcopy(const Arg *dummy)
{
	(void)dummy;

	if (xsel.primary)
		newsource(0, xsel.primary);
}

static void
clippaste(const Arg *dummy)
{
	(void)dummy;

	pasteselection(clipboard_offer);
}

static void
selpaste(const Arg *dummy)
{
	(void)dummy;

	if (primary_offer)
		pasteselection(primary_offer);
	else
		pasteselection(clipboard_offer);
}

/* ---- selection listeners ---- */

static void
offeroffer(void *data, struct wl_data_offer *offer, const char *mime)
{
	offeraddmime(data, mime);
}

static void
primaryofferoffer(void *data, struct zwp_primary_selection_offer_v1 *offer,
                  const char *mime)
{
	offeraddmime(data, mime);
}

static void
dataoffer(void *data, struct wl_data_device *dev, struct wl_data_offer *id)
{
	Offer *o;

	offerdestroy(pending_offer);
	o = xmalloc(sizeof(*o));
	memset(o, 0, sizeof(*o));
	o->data = id;
	wl_data_offer_add_listener(id, &data_offer_listener, o);
	pending_offer = o;
}

static void
dataenter(void *data, struct wl_data_device *dev, uint32_t serial,
          struct wl_surface *s, wl_fixed_t x, wl_fixed_t y,
          struct wl_data_offer *id)
{
	/* Drag and drop is not implemented; drop the offer. */
	if (pending_offer && pending_offer->data == id) {
		offerdestroy(pending_offer);
		pending_offer = NULL;
	}
}

static void
dataleave(void *data, struct wl_data_device *dev)
{
}

static void
datamotion(void *data, struct wl_data_device *dev, uint32_t time,
           wl_fixed_t x, wl_fixed_t y)
{
}

static void
datadrop(void *data, struct wl_data_device *dev)
{
}

static void
dataselection(void *data, struct wl_data_device *dev,
              struct wl_data_offer *id)
{
	if (!id) {
		offerdestroy(clipboard_offer);
		clipboard_offer = NULL;
		return;
	}
	if (pending_offer && pending_offer->data == id) {
		offerdestroy(clipboard_offer);
		clipboard_offer = pending_offer;
		pending_offer = NULL;
	}
}

static void
primaryoffer(void *data, struct zwp_primary_selection_device_v1 *dev,
             struct zwp_primary_selection_offer_v1 *id)
{
	Offer *o;

	offerdestroy(pending_primary_offer);
	o = xmalloc(sizeof(*o));
	memset(o, 0, sizeof(*o));
	o->primary = id;
	zwp_primary_selection_offer_v1_add_listener(id,
	                                            &primary_offer_listener, o);
	pending_primary_offer = o;
}

static void
primaryselection(void *data, struct zwp_primary_selection_device_v1 *dev,
                 struct zwp_primary_selection_offer_v1 *id)
{
	if (!id) {
		offerdestroy(primary_offer);
		primary_offer = NULL;
		return;
	}
	if (pending_primary_offer && pending_primary_offer->primary == id) {
		offerdestroy(primary_offer);
		primary_offer = pending_primary_offer;
		pending_primary_offer = NULL;
	}
}

static void
datasourcesend(void *data, struct wl_data_source *src, const char *mime,
               int32_t fd)
{
	Source *s = data;

	fcntl(fd, F_SETFL, O_NONBLOCK);
	transferadd(fd, 1, s->text, strlen(s->text));
}

static void
datasourcecancelled(void *data, struct wl_data_source *src)
{
	sourcedestroy(data);
}

static void
primarysourcesend(void *data,
                  struct zwp_primary_selection_source_v1 *src,
                  const char *mime, int32_t fd)
{
	Source *s = data;

	fcntl(fd, F_SETFL, O_NONBLOCK);
	transferadd(fd, 1, s->text, strlen(s->text));
}

static void
primarysourcecancelled(void *data,
                       struct zwp_primary_selection_source_v1 *src)
{
	sourcedestroy(data);
}

static const struct wl_data_offer_listener data_offer_listener = {
	.offer = offeroffer,
};

static const struct zwp_primary_selection_offer_v1_listener
primary_offer_listener = {
	.offer = primaryofferoffer,
};

static const struct wl_data_source_listener data_source_listener = {
	.target = NULL,
	.send = datasourcesend,
	.cancelled = datasourcecancelled,
};

static const struct zwp_primary_selection_source_v1_listener
primary_source_listener = {
	.send = primarysourcesend,
	.cancelled = primarysourcecancelled,
};

static const struct wl_data_device_listener data_device_listener = {
	.data_offer = dataoffer,
	.enter = dataenter,
	.leave = dataleave,
	.motion = datamotion,
	.drop = datadrop,
	.selection = dataselection,
};

static const struct zwp_primary_selection_device_v1_listener
primary_device_listener = {
	.data_offer = primaryoffer,
	.selection = primaryselection,
};

/* ---- input method ---- */

static void
imespotsend(void)
{
	int x, y;

	if (!text_input || !ime_enabled)
		return;

	x = win.hborderpx + ime_x * win.cw;
	y = win.vborderpx + ime_y * win.ch;
	zwp_text_input_v3_set_cursor_rectangle(text_input, tological(x),
	                                       tological(y), tological(win.cw),
	                                       tological(win.ch));
	zwp_text_input_v3_commit(text_input);
}

static void
imeenable(void)
{
	if (!text_input || ime_enabled)
		return;

	zwp_text_input_v3_enable(text_input);
	zwp_text_input_v3_set_content_type(text_input,
	                                   ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
	                                   ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
	ime_enabled = 1;
	imespotsend();
}

static void
imedisable(void)
{
	if (!text_input || !ime_enabled)
		return;

	zwp_text_input_v3_disable(text_input);
	zwp_text_input_v3_commit(text_input);
	ime_enabled = 0;
	ime_active = 0;
}

void
imespot(int x, int y)
{
	ime_x = x;
	ime_y = y;
	imespotsend();
}

static void
textinputenter(void *data, struct zwp_text_input_v3 *ti,
               struct wl_surface *s)
{
}

static void
textinputleave(void *data, struct zwp_text_input_v3 *ti,
               struct wl_surface *s)
{
	ime_active = 0;
}

static void
textinputpreedit(void *data, struct zwp_text_input_v3 *ti, const char *text,
                 int32_t begin, int32_t end)
{
	/* The preedit is not rendered here; it only gates key handling. */
	pendingdiscard();
	ime_active = text != NULL && *text != '\0';
}

static void
textinputcommit(void *data, struct zwp_text_input_v3 *ti, const char *text)
{
	pendingdiscard();
	if (!text || !*text)
		return;

	ttywrite(text, strlen(text), 1);
	ime_active = 0;
}

static void
textinputdelete(void *data, struct zwp_text_input_v3 *ti, uint32_t before,
                uint32_t after)
{
}

static void
textinputdone(void *data, struct zwp_text_input_v3 *ti, uint32_t serial)
{
}

static void
textinputaction(void *data, struct zwp_text_input_v3 *ti, uint32_t action,
                uint32_t serial)
{
}

static void
textinputlanguage(void *data, struct zwp_text_input_v3 *ti, const char *lang)
{
}

static void
textinputpreedithint(void *data, struct zwp_text_input_v3 *ti,
                     uint32_t start, uint32_t end, uint32_t hint)
{
}

static const struct zwp_text_input_v3_listener text_input_listener = {
	.enter = textinputenter,
	.leave = textinputleave,
	.preedit_string = textinputpreedit,
	.commit_string = textinputcommit,
	.delete_surrounding_text = textinputdelete,
	.done = textinputdone,
	.action = textinputaction,
	.language = textinputlanguage,
	.preedit_hint = textinputpreedithint,
};

/* ---- keyboard ---- */

static uint
modmask(void)
{
	uint mask = 0;

	if (!xkbstate)
		return 0;

	if (shift_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, shift_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= ShiftMask;
	if (lock_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, lock_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= LockMask;
	if (ctrl_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, ctrl_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= ControlMask;
	if (mod1_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, mod1_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= Mod1Mask;
	if (mod2_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, mod2_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= Mod2Mask;
	if (mod3_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, mod3_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= Mod3Mask;
	if (mod4_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, mod4_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= Mod4Mask;
	if (mod5_idx >= 0 && xkb_state_mod_index_is_active(
	        xkbstate, mod5_idx, XKB_STATE_MODS_EFFECTIVE))
		mask |= Mod5Mask;

	return mask;
}

static void
updatemods(void)
{
	uint mask;

	if (!xkbstate)
		return;
	mask = modmask();
	MODBIT(win.mode, (mask & Mod2Mask) != 0, MODE_NUMLOCK);
}

static int
match(uint mask, uint state)
{
	return mask == MOD_ANY || mask == (state & ~ignoremod);
}

static char *
kmap(KeySym k, uint state)
{
	Key *kp;
	int i;

	/* Check for mapped keys out of X11 function keys. */
	for (i = 0; i < LEN(mappedkeys); i++) {
		if (mappedkeys[i] == k)
			break;
	}
	if (i == LEN(mappedkeys)) {
		if ((k & 0xFFFF) < 0xFD00)
			return NULL;
	}

	for (kp = key; kp < key + LEN(key); kp++) {
		if (kp->k != k)
			continue;

		if (!match(kp->mask, state))
			continue;

		if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
			continue;
		if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2)
			continue;

		if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
			continue;

		return kp->s;
	}

	return NULL;
}

/* xkbcommon returns the level text; apply the classic Ctrl mapping. */
static int
ctrlfilter(char *buf, int len)
{
	uchar c;

	if (len != 1 || !(modmask() & ControlMask))
		return len;

	c = buf[0];
	if (c >= '@' && c <= '_')
		buf[0] = c & 0x1f;
	else if (c >= 'a' && c <= 'z')
		buf[0] = c & 0x1f;
	else if (c == '?')
		buf[0] = 0x7f;
	else if (c == ' ')
		buf[0] = 0;

	return len;
}

static int
keytext(xkb_keycode_t keycode, char *buf, int len)
{
	enum xkb_compose_status status;
	KeySym sym;
	int n;

	sym = xkb_state_key_get_one_sym(xkbstate, keycode);

	if (composestate) {
		xkb_compose_state_feed(composestate, sym);
		status = xkb_compose_state_get_status(composestate);
		if (status == XKB_COMPOSE_COMPOSING)
			return 0;
		if (status == XKB_COMPOSE_COMPOSED) {
			n = xkb_compose_state_get_utf8(composestate, buf, len);
			xkb_compose_state_reset(composestate);
			return n > 0 ? ctrlfilter(buf, n) : 0;
		}
		if (status == XKB_COMPOSE_CANCELLED) {
			xkb_compose_state_reset(composestate);
			return 0;
		}
	}

	n = xkb_state_key_get_utf8(xkbstate, keycode, buf, len);
	if (n <= 0)
		return 0;

	return ctrlfilter(buf, n);
}

/* Input methods see the same key events we do and react a few milliseconds
 * later.  Hold printable text back for IMEWAIT ms: if the IME produces a
 * preedit or commit in the meantime, the key belonged to it and is dropped. */
static void
pendingflush(void)
{
	if (!pendinglen)
		return;

	ttywrite(pendingtext, pendinglen, 1);
	pendinglen = 0;
}

static void
pendingdiscard(void)
{
	pendinglen = 0;
}

static void
textqueue(const char *buf, size_t len)
{
	struct timespec now;

	if (pendinglen + len > pendingcap) {
		pendingcap = MAX(64, pendinglen + len);
		pendingtext = xrealloc(pendingtext, pendingcap);
	}

	if (!pendinglen) {
		clock_gettime(CLOCK_MONOTONIC, &now);
		pendinguntil = now;
		pendinguntil.tv_nsec += IMEWAIT * 1000000L;
		if (pendinguntil.tv_nsec >= 1000000000L) {
			pendinguntil.tv_sec++;
			pendinguntil.tv_nsec -= 1000000000L;
		}
	}

	memcpy(pendingtext + pendinglen, buf, len);
	pendinglen += len;
}

static int
hascontrol(const char *buf, int len)
{
	int i;

	for (i = 0; i < len; i++)
		if ((uchar)buf[i] < 0x20 || buf[i] == 0x7f)
			return 1;
	return 0;
}

static void
keypress(xkb_keycode_t keycode)
{
	KeySym sym;
	uint state;
	char buf[64];
	int len;
	Shortcut *bp;
	char *customkey;

	if (pointer && !pointer_hidden)
		hidepointer(pointer_serial ? pointer_serial : last_serial);

	if (IS_SET(MODE_KBDLOCK))
		return;

	if (ime_active)
		return;

	sym = xkb_state_key_get_one_sym(xkbstate, keycode);
	state = modmask();
	if (IS_SET(MODE_KBDSELECT)) {
		pendingflush();
		len = keytext(keycode, buf, sizeof(buf));
		if (match(MOD_NONE, state) || (state & ShiftMask))
			win.mode ^= trt_kbdselect(sym, buf, len);
		return;
	}

	/* 1. shortcuts */
	for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
		if (sym == bp->keysym && match(bp->mod, state)) {
			pendingflush();
			bp->func(&(bp->arg));
			return;
		}
	}

	/* 2. custom keys from config.h */
	if ((customkey = kmap(sym, state))) {
		pendingflush();
		ttywrite(customkey, strlen(customkey), 1);
		return;
	}

	/* 3. composed string from input method */
	len = keytext(keycode, buf, sizeof(buf));
	if (len == 0)
		return;

	if (len == 1 && state & Mod1Mask) {
		pendingflush();
		if (IS_SET(MODE_8BIT)) {
			if (*buf < 0177) {
				Rune c = *buf | 0x80;

				len = utf8encode(c, buf);
			}
		} else {
			buf[1] = buf[0];
			buf[0] = '\033';
			len = 2;
		}
	}

	if (!text_input || hascontrol(buf, len)) {
		pendingflush();
		ttywrite(buf, len, 1);
	} else {
		textqueue(buf, len);
	}
}

static void
startrepeat(xkb_keycode_t keycode)
{
	struct itimerspec ts;

	if (!xkbkeymap || !repeatrate ||
	    !xkb_keymap_key_repeats(xkbkeymap, keycode)) {
		stoprepeat();
		return;
	}

	if (repeatfd < 0) {
		repeatfd = timerfd_create(CLOCK_MONOTONIC,
		                          TFD_NONBLOCK | TFD_CLOEXEC);
		if (repeatfd < 0)
			return;
	}

	repeatkey = keycode;
	memset(&ts, 0, sizeof(ts));
	ts.it_value.tv_sec = repeatdelay / 1000;
	ts.it_value.tv_nsec = (repeatdelay % 1000) * 1000000L;
	ts.it_interval.tv_nsec = 1000000000L / repeatrate;
	timerfd_settime(repeatfd, 0, &ts, NULL);
}

static void
stoprepeat(void)
{
	struct itimerspec ts;

	repeatkey = 0;
	if (repeatfd < 0)
		return;

	memset(&ts, 0, sizeof(ts));
	timerfd_settime(repeatfd, 0, &ts, NULL);
}

static void
repeattick(void)
{
	uint64_t exp;

	if (repeatfd < 0 || !repeatkey)
		return;
	if (read(repeatfd, &exp, sizeof(exp)) != sizeof(exp))
		return;
	keypress(repeatkey);
}

static void
numlock(const Arg *dummy)
{
	(void)dummy;
	win.mode ^= MODE_NUMLOCK;
}

/* ---- cursor shape ---- */

static uint32_t
cursorshapeid(void)
{
	const char *s = mouseshape;

	if (!strcmp(s, "text"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
	if (!strcmp(s, "pointer") || !strcmp(s, "hand1") ||
	    !strcmp(s, "hand2"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
	if (!strcmp(s, "crosshair"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
	if (!strcmp(s, "wait"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
	if (!strcmp(s, "progress"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS;
	if (!strcmp(s, "move") || !strcmp(s, "fleur"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
	if (!strcmp(s, "ew-resize") || !strcmp(s, "sb_h_double_arrow"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
	if (!strcmp(s, "ns-resize") || !strcmp(s, "sb_v_double_arrow"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
	if (!strcmp(s, "nw-resize"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE;
	if (!strcmp(s, "ne-resize"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE;
	if (!strcmp(s, "sw-resize"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE;
	if (!strcmp(s, "se-resize"))
		return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE;
	return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
}

static void
setcursorshape(uint32_t serial)
{
	if (!cursor_shape)
		return;
	wp_cursor_shape_device_v1_set_shape(cursor_shape, serial,
	                                    cursorshapeid());
}

static void
hidepointer(uint32_t serial)
{
	if (!pointer || !cursor_shape || pointer_hidden)
		return;
	wl_pointer_set_cursor(pointer, serial, NULL, 0, 0);
	pointer_hidden = 1;
}

static void
showpointer(uint32_t serial)
{
	if (!pointer)
		return;
	if (cursor_shape)
		setcursorshape(serial);
	pointer_hidden = 0;
}

/* ---- seat and keyboard ---- */

static void
seatcapabilities(void *data, struct wl_seat *s, uint32_t caps)
{
	if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
		pointer = wl_seat_get_pointer(s);
		wl_pointer_add_listener(pointer, &pointer_listener, NULL);
		if (cursor_shape_manager)
			cursor_shape = wp_cursor_shape_manager_v1_get_pointer(
				cursor_shape_manager, pointer);
	} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && pointer) {
		if (cursor_shape) {
			wp_cursor_shape_device_v1_destroy(cursor_shape);
			cursor_shape = NULL;
		}
		wl_pointer_destroy(pointer);
		pointer = NULL;
	}

	if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
		keyboard = wl_seat_get_keyboard(s);
		wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
	} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
		wl_keyboard_destroy(keyboard);
		keyboard = NULL;
	}
}

static void
seatname(void *data, struct wl_seat *s, const char *name)
{
}

static void
keyboardkeymap(void *data, struct wl_keyboard *kb, uint32_t format,
               int32_t fd, uint32_t size)
{
	struct xkb_keymap *keymap;
	char *map;

	if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
		close(fd);
		return;
	}

	map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (map == MAP_FAILED) {
		close(fd);
		return;
	}

	keymap = xkb_keymap_new_from_string(xkbcontext, map,
	                                    XKB_KEYMAP_FORMAT_TEXT_V1,
	                                    XKB_KEYMAP_COMPILE_NO_FLAGS);
	munmap(map, size);
	close(fd);

	if (!keymap)
		return;

	if (xkbstate)
		xkb_state_unref(xkbstate);
	if (xkbkeymap)
		xkb_keymap_unref(xkbkeymap);
	xkbkeymap = keymap;
	xkbstate = xkb_state_new(xkbkeymap);
	if (!xkbstate)
		die("failed to create xkb state\n");

	shift_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_SHIFT);
	lock_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_CAPS);
	ctrl_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_CTRL);
	mod1_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_ALT);
	mod2_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_NUM);
	mod3_idx = xkb_keymap_mod_get_index(xkbkeymap, "Mod3");
	mod4_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_LOGO);
	mod5_idx = xkb_keymap_mod_get_index(xkbkeymap, "Mod5");
}

static void
keyboardenter(void *data, struct wl_keyboard *kb, uint32_t serial,
              struct wl_surface *s, struct wl_array *keys)
{
	last_serial = serial;
	win.mode |= MODE_FOCUSED;
	if (IS_SET(MODE_FOCUS))
		ttywrite("\033[I", 3, 0);
	imeenable();
}

static void
keyboardleave(void *data, struct wl_keyboard *kb, uint32_t serial,
              struct wl_surface *s)
{
	last_serial = serial;
	pendingflush();
	win.mode &= ~MODE_FOCUSED;
	if (IS_SET(MODE_FOCUS))
		ttywrite("\033[O", 3, 0);
	stoprepeat();
	imedisable();
}

static void
keyboardkey(void *data, struct wl_keyboard *kb, uint32_t serial,
            uint32_t time, uint32_t key, uint32_t state)
{
	xkb_keycode_t keycode = key + 8;

	last_serial = serial;

	if (!xkbstate)
		return;

	if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
		xkb_state_update_key(xkbstate, keycode, XKB_KEY_DOWN);
		updatemods();
		keypress(keycode);
		startrepeat(keycode);
	} else {
		xkb_state_update_key(xkbstate, keycode, XKB_KEY_UP);
		updatemods();
		if (keycode == repeatkey)
			stoprepeat();
	}
}

static void
keyboardmodifiers(void *data, struct wl_keyboard *kb, uint32_t serial,
                  uint32_t depressed, uint32_t latched, uint32_t locked,
                  uint32_t group)
{
	last_serial = serial;
	if (!xkbstate)
		return;

	xkb_state_update_mask(xkbstate, depressed, latched, locked, 0, 0, group);
	updatemods();
}

static void
keyboardrepeat(void *data, struct wl_keyboard *kb, int32_t rate,
               int32_t delay)
{
	if (rate <= 0 || delay <= 0) {
		repeatrate = 0;
		stoprepeat();
		return;
	}
	repeatrate = rate;
	repeatdelay = delay;
	if (repeatkey)
		startrepeat(repeatkey);
}

static const struct wl_seat_listener seat_listener = {
	.capabilities = seatcapabilities,
	.name = seatname,
};

static const struct wl_keyboard_listener keyboard_listener = {
	.keymap = keyboardkeymap,
	.enter = keyboardenter,
	.leave = keyboardleave,
	.key = keyboardkey,
	.modifiers = keyboardmodifiers,
	.repeat_info = keyboardrepeat,
};

/* ---- pointer ---- */

static void
pointerenter(void *data, struct wl_pointer *p, uint32_t serial,
             struct wl_surface *s, wl_fixed_t sx, wl_fixed_t sy)
{
	pointer_serial = serial;
	last_serial = serial;
	pointer_x = topx(wl_fixed_to_double(sx));
	pointer_y = topx(wl_fixed_to_double(sy));
	showpointer(serial);
}

static void
pointerleave(void *data, struct wl_pointer *p, uint32_t serial,
             struct wl_surface *s)
{
	pointer_serial = 0;
}

static void
pointermotion(void *data, struct wl_pointer *p, uint32_t time,
              wl_fixed_t sx, wl_fixed_t sy)
{
	MouseEvent ev;

	memset(&ev, 0, sizeof(ev));
	ev.type = 0;
	ev.x = pointer_x = topx(wl_fixed_to_double(sx));
	ev.y = pointer_y = topx(wl_fixed_to_double(sy));
	ev.state = buttonstate | modmask();
	ev.time = time;
	bmotion(&ev);
}

static void
pointerbutton(void *data, struct wl_pointer *p, uint32_t serial,
              uint32_t time, uint32_t button, uint32_t state)
{
	MouseEvent ev;
	uint b;

	switch (button) {
	case BTN_LEFT:
		b = Button1;
		break;
	case BTN_MIDDLE:
		b = Button2;
		break;
	case BTN_RIGHT:
		b = Button3;
		break;
	case BTN_SIDE:
		b = 8;
		break;
	case BTN_EXTRA:
		b = 9;
		break;
	default:
		return;
	}

	pointer_serial = serial;
	last_serial = serial;

	memset(&ev, 0, sizeof(ev));
	ev.x = pointer_x;
	ev.y = pointer_y;
	ev.button = b;
	ev.serial = serial;
	ev.time = time;
	ev.state = buttonstate | modmask();

	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
		ev.type = 1;
		bpress(&ev);
		buttonstate |= buttonmask(b);
	} else {
		ev.type = 2;
		brelease(&ev);
		buttonstate &= ~buttonmask(b);
	}
}

static void
pointeraxis(void *data, struct wl_pointer *p, uint32_t time, uint32_t axis,
            wl_fixed_t value)
{
	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
		axis_y += wl_fixed_to_double(value);
	else
		axis_x += wl_fixed_to_double(value);
}

static void
pointeraxisdisc(void *data, struct wl_pointer *p, uint32_t axis,
                int32_t value)
{
	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
		axis_dy += value;
	else
		axis_dx += value;
}

static void
pointeraxissource(void *data, struct wl_pointer *p, uint32_t source)
{
}

static void
pointeraxisstop(void *data, struct wl_pointer *p, uint32_t time,
                uint32_t axis)
{
	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
		axis_dy = 0;
		axis_y = 0;
	} else {
		axis_dx = 0;
		axis_x = 0;
	}
}

static void
wheel(uint button)
{
	MouseEvent ev;

	memset(&ev, 0, sizeof(ev));
	ev.x = pointer_x;
	ev.y = pointer_y;
	ev.button = button;
	ev.state = buttonstate | modmask();
	ev.type = 1;
	bpress(&ev);
	ev.type = 2;
	brelease(&ev);
}

static void
pointerframe(void *data, struct wl_pointer *p)
{
	int n, i;

	if (axis_dy) {
		n = MIN(axis_dy < 0 ? -axis_dy : axis_dy, 8);
		for (i = 0; i < n; i++)
			wheel(axis_dy < 0 ? Button4 : Button5);
	} else if (axis_y <= -10.0 || axis_y >= 10.0) {
		wheel(axis_y < 0 ? Button4 : Button5);
	}

	if (axis_dx) {
		n = MIN(axis_dx < 0 ? -axis_dx : axis_dx, 8);
		for (i = 0; i < n; i++)
			wheel(axis_dx < 0 ? Button6 : Button7);
	} else if (axis_x <= -10.0 || axis_x >= 10.0) {
		wheel(axis_x < 0 ? Button6 : Button7);
	}

	axis_dy = 0;
	axis_dx = 0;
	axis_y = 0;
	axis_x = 0;
}

static const struct wl_pointer_listener pointer_listener = {
	.enter = pointerenter,
	.leave = pointerleave,
	.motion = pointermotion,
	.button = pointerbutton,
	.axis = pointeraxis,
	.frame = pointerframe,
	.axis_source = pointeraxissource,
	.axis_stop = pointeraxisstop,
	.axis_discrete = pointeraxisdisc,
};

void
setpointermotion(int set)
{
	/* The compositor always sends motion events. */
	(void)set;
}

/* ---- mouse selection and reporting ---- */

static uint
buttonmask(uint button)
{
	return button == Button1 ? Button1Mask
	     : button == Button2 ? Button2Mask
	     : button == Button3 ? Button3Mask
	     : button == Button4 ? Button4Mask
	     : button == Button5 ? Button5Mask
	     : 0;
}

static int
evcol(MouseEvent *e)
{
	int x = e->x - win.hborderpx;

	LIMIT(x, 0, win.tw - 1);
	return x / win.cw;
}

static int
evrow(MouseEvent *e)
{
	int y = e->y - win.vborderpx;

	LIMIT(y, 0, win.th - 1);
	return y / win.ch;
}

static void
mousesel(MouseEvent *e, int done)
{
	int type, seltype = SEL_REGULAR;
	uint state = e->state & ~(Button1Mask | forcemousemod);

	for (type = 1; type < LEN(selmasks); ++type) {
		if (match(selmasks[type], state)) {
			seltype = type;
			break;
		}
	}
	selextend(evcol(e), evrow(e), seltype, done);
	if (done)
		setsel(getsel());
}

static void
mousereport(MouseEvent *e)
{
	int len, x = evcol(e), y = evrow(e),
	    button = e->button, state = e->state;
	char buf[40];
	static int ox, oy;

	/* from urxvt */
	if (e->type == 0) {
		if (x == ox && y == oy)
			return;
		if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
			return;
		/* MOUSE_MOTION: no reporting if no button is pressed */
		if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
			return;

		button = oldbutton + 32;
		ox = x;
		oy = y;
	} else {
		if (!IS_SET(MODE_MOUSESGR) && e->type == 2) {
			button = 3;
		} else {
			button -= Button1;
			if (button >= 3)
				button += 64 - 3;
		}
		if (e->type == 1) {
			oldbutton = button;
			ox = x;
			oy = y;
		} else if (e->type == 2) {
			oldbutton = 3;
			/* MODE_MOUSEX10: no button release reporting */
			if (IS_SET(MODE_MOUSEX10))
				return;
			if (button == 64 || button == 65)
				return;
		}
	}

	if (!IS_SET(MODE_MOUSEX10)) {
		button += ((state & ShiftMask) ? 4 : 0)
		        + ((state & Mod4Mask) ? 8 : 0)
		        + ((state & ControlMask) ? 16 : 0);
	}

	if (IS_SET(MODE_MOUSESGR)) {
		len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
		               button, x + 1, y + 1,
		               e->type == 2 ? 'm' : 'M');
	} else if (x < 223 && y < 223) {
		len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
		               32 + button, 32 + x + 1, 32 + y + 1);
	} else {
		return;
	}

	ttywrite(buf, len, 0);
}

static int
mouseaction(MouseEvent *e, uint release)
{
	MouseShortcut *ms;

	/* ignore Button<N>mask for Button<N> - it's set on release */
	uint state = e->state & ~buttonmask(e->button);

	for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
		if (ms->release == release &&
		    ms->button == e->button &&
		    (match(ms->mod, state) ||  /* exact or forced */
		     match(ms->mod, state & ~forcemousemod))) {
			ms->func(&(ms->arg));
			return 1;
		}
	}

	return 0;
}

static void
bpress(MouseEvent *e)
{
	struct timespec now;
	int snap;

	if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
		mousereport(e);
		return;
	}

	if (mouseaction(e, 0))
		return;

	if (e->button == Button1) {
		/*
		 * If the user clicks below predefined timeouts specific
		 * snapping behaviour is exposed.
		 */
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout)
			snap = SNAP_LINE;
		else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout)
			snap = SNAP_WORD;
		else
			snap = 0;
		xsel.tclick2 = xsel.tclick1;
		xsel.tclick1 = now;

		selstart(evcol(e), evrow(e), snap);
	}
}

static void
brelease(MouseEvent *e)
{
	if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
		mousereport(e);
		return;
	}

	if (mouseaction(e, 1))
		return;
	if (e->button == Button1)
		mousesel(e, 1);
}

static void
bmotion(MouseEvent *e)
{
	if (pointer_hidden)
		showpointer(pointer_serial ? pointer_serial : last_serial);

	if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
		mousereport(e);
		return;
	}

	mousesel(e, 0);
}

/* ---- st.c facing helpers ---- */

static void
zoom(const Arg *arg)
{
	Arg larg;

	larg.f = usedfontsize + arg->f;
	zoomabs(&larg);
}

static void
zoomabs(const Arg *arg)
{
	unloadfonts();
	loadfonts(usedfont, arg->f);
	if (started)
		cresize(win.lw, win.lh);
	redraw();
}

static void
zoomreset(const Arg *arg)
{
	Arg larg;

	(void)arg;
	if (defaultfontsize > 0) {
		larg.f = defaultfontsize;
		zoomabs(&larg);
	}
}

static void
ttysend(const Arg *arg)
{
	ttywrite(arg->s, strlen(arg->s), 1);
}

void
bell(void)
{
	if (bellvolume && systembell)
		xdg_system_bell_v1_ring(systembell, surface);
}

void
setmode(int set, unsigned int flags)
{
	int mode = win.mode;

	MODBIT(win.mode, set, flags);
	if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
		redraw();
}

int
setcursor(int cursor)
{
	if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
		return 1;
	win.cursor = cursor;
	return 0;
}

void
settitle(char *p)
{
	DEFAULT(p, opt_title);
	if (!p)
		p = "st";
	xdg_toplevel_set_title(xdg_toplevel, p);
}

void
toggle_winmode(int flag)
{
	win.mode ^= flag;
}

void
keyboard_select(const Arg *dummy)
{
	(void)dummy;
	win.mode ^= trt_kbdselect(-1, NULL, 0);
}

/* ---- registry / setup ---- */

static void
registryglobal(void *data, struct wl_registry *reg, uint32_t name,
               const char *interface, uint32_t version)
{
	if (!strcmp(interface, wl_compositor_interface.name)) {
		compositor = wl_registry_bind(reg, name, &wl_compositor_interface,
		                              MIN(version, 6));
	} else if (!strcmp(interface, wl_shm_interface.name)) {
		shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
		wl_shm_add_listener(shm, &shm_listener, NULL);
	} else if (!strcmp(interface, xdg_wm_base_interface.name)) {
		wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface,
		                           MIN(version, 6));
		xdg_wm_base_add_listener(wm_base, &wm_base_listener, NULL);
	} else if (!strcmp(interface, wl_seat_interface.name)) {
		seat = wl_registry_bind(reg, name, &wl_seat_interface,
		                        MIN(version, 7));
	} else if (!strcmp(interface, wl_data_device_manager_interface.name)) {
		data_manager = wl_registry_bind(reg, name,
		                                &wl_data_device_manager_interface, 1);
	} else if (!strcmp(interface,
	                   zwp_primary_selection_device_manager_v1_interface.name)) {
		primary_manager = wl_registry_bind(reg, name,
			&zwp_primary_selection_device_manager_v1_interface, 1);
	} else if (!strcmp(interface, zwp_text_input_manager_v3_interface.name)) {
		text_input_manager = wl_registry_bind(reg, name,
			&zwp_text_input_manager_v3_interface, 1);
	} else if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name)) {
		cursor_shape_manager = wl_registry_bind(reg, name,
			&wp_cursor_shape_manager_v1_interface, 1);
	} else if (!strcmp(interface,
	                   wp_fractional_scale_manager_v1_interface.name)) {
		fractional_manager = wl_registry_bind(reg, name,
			&wp_fractional_scale_manager_v1_interface, 1);
	} else if (!strcmp(interface, wp_viewporter_interface.name)) {
		viewporter = wl_registry_bind(reg, name, &wp_viewporter_interface, 1);
	} else if (!strcmp(interface, xdg_system_bell_v1_interface.name)) {
		systembell = wl_registry_bind(reg, name,
		                              &xdg_system_bell_v1_interface, 1);
	}
}

static void
registryglobalremove(void *data, struct wl_registry *reg, uint32_t name)
{
}

static void
shmformat(void *data, struct wl_shm *s, uint32_t format)
{
	if (format == WL_SHM_FORMAT_ARGB8888)
		shm_argb = 1;
}

static const struct wl_registry_listener registry_listener = {
	.global = registryglobal,
	.global_remove = registryglobalremove,
};

static const struct wl_shm_listener shm_listener = {
	.format = shmformat,
};

static void
flushdisplay(void)
{
	struct pollfd pfd;

	pfd.fd = wl_display_get_fd(display);
	pfd.events = POLLOUT;

	while (wl_display_flush(display) < 0) {
		if (errno != EAGAIN)
			die("wl_display_flush: %s\n", strerror(errno));
		if (poll(&pfd, 1, -1) < 0 && errno != EINTR)
			die("poll: %s\n", strerror(errno));
	}
}

static void
wlinit(int ncols, int nrows)
{
	int bw;

	signal(SIGPIPE, SIG_IGN);

	display = wl_display_connect(NULL);
	if (!display)
		die("can't connect to Wayland display\n");

	registry = wl_display_get_registry(display);
	wl_registry_add_listener(registry, &registry_listener, NULL);
	wl_display_roundtrip(display);

	if (!compositor)
		die("compositor does not support wl_compositor\n");
	if (!wm_base)
		die("compositor does not support xdg_wm_base\n");
	if (!shm)
		die("compositor does not support wl_shm\n");

	xkbcontext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
	if (!xkbcontext)
		die("failed to create xkb context\n");
	composetable = xkb_compose_table_new_from_locale(xkbcontext,
	                   setlocale(LC_CTYPE, NULL),
	                   XKB_COMPOSE_COMPILE_NO_FLAGS);
	if (composetable)
		composestate = xkb_compose_state_new(composetable,
	                                         XKB_COMPOSE_STATE_NO_FLAGS);

	if (seat) {
		wl_seat_add_listener(seat, &seat_listener, NULL);
		if (data_manager) {
			data_device = wl_data_device_manager_get_data_device(
				data_manager, seat);
			wl_data_device_add_listener(data_device,
			                            &data_device_listener, NULL);
		}
		if (primary_manager) {
			primary_device =
				zwp_primary_selection_device_manager_v1_get_device(
					primary_manager, seat);
			zwp_primary_selection_device_v1_add_listener(
				primary_device, &primary_device_listener, NULL);
		}
		if (text_input_manager) {
			text_input = zwp_text_input_manager_v3_get_text_input(
				text_input_manager, seat);
			zwp_text_input_v3_add_listener(text_input,
			                               &text_input_listener, NULL);
		}
	}

	/* seat capabilities and wl_shm formats */
	wl_display_roundtrip(display);

	if (!shm_argb)
		die("compositor does not support WL_SHM_FORMAT_ARGB8888\n");

	if (!FcInit())
		die("could not init fontconfig.\n");
	if (FT_Init_FreeType(&ftlib))
		die("could not init freetype.\n");

	surface = wl_compositor_create_surface(compositor);
	wl_surface_add_listener(surface, &surface_listener, NULL);
	xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
	xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
	xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
	xdg_toplevel_set_app_id(xdg_toplevel, opt_class ? opt_class : termname);
	settitle(NULL);

	if (fractional_manager) {
		fractional = wp_fractional_scale_manager_v1_get_fractional_scale(
			fractional_manager, surface);
		wp_fractional_scale_v1_add_listener(fractional,
		                                    &fractional_listener, NULL);
	}
	if (viewporter && fractional_manager)
		viewport = wp_viewporter_get_viewport(viewporter, surface);

	/* An empty commit is required for the initial configure. */
	wl_surface_commit(surface);
	while (!configured && wl_display_dispatch(display) != -1)
		;
	if (!configured)
		die("compositor did not send xdg_surface.configure\n");

	/* Pick up fractional-scale / preferred_buffer_scale. */
	wl_display_roundtrip(display);

	if (have_frac_scale)
		scale = frac_scale / 120.0;
	else if (surface_scale > 0)
		scale = surface_scale;
	else
		scale = 1.0;
	use_viewport = have_frac_scale && viewporter != NULL;

	loadcols();
	usedfont = opt_font ? opt_font : font;
	loadfonts(usedfont, 0);

	if (win.lw <= 0 || win.lh <= 0) {
		bw = borderpxdev();
		win.lw = (int)ceil((2.0 * bw + ncols * win.cw) / scale);
		win.lh = (int)ceil((2.0 * bw + nrows * win.ch) / scale);
	}
	updatesize();

	wl_display_flush(display);
}

/* ---- main loop ---- */

static void
run(void)
{
	int ttyfd, drawing, npfd, xev, i;
	struct timespec now, lastblink, trigger;
	struct pollfd *pfds = NULL;
	Transfer *t;
	double timeout;

	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);

	started = 1;
	cresize(win.lw, win.lh);
	draw();
	flushdisplay();

	clock_gettime(CLOCK_MONOTONIC, &lastblink);

	for (timeout = -1, drawing = 0;;) {
		npfd = 3;
		for (t = transfers; t; t = t->next)
			npfd++;

		pfds = xrealloc(pfds, npfd * sizeof(*pfds));
		memset(pfds, 0, npfd * sizeof(*pfds));
		pfds[0].fd = wl_display_get_fd(display);
		pfds[0].events = POLLIN;
		pfds[1].fd = ttyfd;
		pfds[1].events = POLLIN;
		pfds[2].fd = repeatfd;
		pfds[2].events = POLLIN;
		i = 3;
		for (t = transfers; t; t = t->next, i++) {
			pfds[i].fd = t->fd;
			pfds[i].events = t->writing ? POLLOUT : POLLIN;
		}

		if (wl_display_dispatch_pending(display) > 0)
			timeout = 0;  /* existing events might not set the fd */

		flushdisplay();

		if (pendinglen) {
			struct timespec pnow;
			double left;

			clock_gettime(CLOCK_MONOTONIC, &pnow);
			left = TIMEDIFF(pendinguntil, pnow);
			if (left <= 0)
				pendingflush();
			else if (timeout < 0 || left < timeout)
				timeout = left;
		}

		if (poll(pfds, npfd, timeout < 0 ? -1 : (int)timeout) < 0) {
			if (errno == EINTR)
				continue;
			die("poll failed: %s\n", strerror(errno));
		}
		clock_gettime(CLOCK_MONOTONIC, &now);

		xev = 0;

		if (pfds[1].revents)
			ttyread();
		if (pfds[2].revents)
			repeattick();
		if (npfd > 3)
			transferspoll(pfds + 3);
		if (pfds[0].revents) {
			if (wl_display_dispatch(display) < 0)
				die("wayland connection lost\n");
			xev = 1;
		}
		if (pending_event) {
			pending_event = 0;
			xev = 1;
		}

		/*
		 * To reduce flicker and tearing, when new content or event
		 * triggers drawing, we first wait a bit to ensure we got
		 * everything, and if nothing new arrives - we draw.
		 * We start with trying to wait minlatency ms. If more content
		 * arrives sooner, we retry with shorter and shorter periods,
		 * and eventually draw even without idle after maxlatency ms.
		 */
		if (pfds[1].revents || xev) {
			if (!drawing) {
				trigger = now;
				drawing = 1;
			}
			timeout = (maxlatency - TIMEDIFF(now, trigger)) /
			          maxlatency * minlatency;
			if (timeout > 0)
				continue;  /* we have time, try to find idle */
		}

		/* idle detected or maxlatency exhausted -> draw */
		timeout = -1;
		if (blinktimeout && tattrset(ATTR_BLINK)) {
			timeout = blinktimeout - TIMEDIFF(now, lastblink);
			if (timeout <= 0) {
				if (-timeout > blinktimeout) /* start visible */
					win.mode |= MODE_BLINK;
				win.mode ^= MODE_BLINK;
				tsetdirtattr(ATTR_BLINK);
				lastblink = now;
				timeout = blinktimeout;
			}
		}

		draw();
		flushdisplay();
		drawing = 0;
	}
}

static void
usage(void)
{
	die("usage: %s [-aiv] [-A alpha] [-c class] [-f font] [-g geometry]"
	    " [-o file]\n"
	    "          [-T title] [-t title]"
	    " [[-e] command [args ...]]\n"
	    "       %s [-aiv] [-A alpha] [-c class] [-f font] [-g geometry]"
	    " [-o file]\n"
	    "          [-T title] [-t title] -l line"
	    " [stty_args ...]\n", argv0, argv0);
}

static void
parsegeometry(char *s)
{
	char *p;
	long w, h;

	w = strtol(s, &p, 10);
	h = 0;
	if (*p == 'x' || *p == 'X')
		h = strtol(p + 1, &p, 10);

	if (w > 0)
		cols = w;
	if (h > 0)
		rows = h;

	if (*p == '+' || *p == '-')
		fprintf(stderr, "%s: ignoring window position in -g\n",
		        argv0);
}

int
main(int argc, char *argv[])
{
	win.mode = MODE_NUMLOCK;
	setcursor(cursorshape);

	ARGBEGIN {
	case 'a':
		allowaltscreen = 0;
		break;
	case 'A':
		opt_alpha = EARGF(usage());
		break;
	case 'c':
		opt_class = EARGF(usage());
		break;
	case 'e':
		if (argc > 0)
			--argc, ++argv;
		goto run;
	case 'f':
		opt_font = EARGF(usage());
		break;
	case 'g':
		parsegeometry(EARGF(usage()));
		break;
	case 'i':
		opt_fixed = 1;
		break;
	case 'l':
		opt_line = EARGF(usage());
		break;
	case 'o':
		opt_io = EARGF(usage());
		break;
	case 't':
	case 'T':
		opt_title = EARGF(usage());
		break;
	case 'v':
		die("%s " VERSION "\n", argv0);
		break;
	default:
		usage();
	} ARGEND;

run:
	if (argc > 0) /* eat all remaining arguments */
		opt_cmd = argv;

	if (!opt_title)
		opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];

	setlocale(LC_CTYPE, "");
	cols = MAX(cols, 1);
	rows = MAX(rows, 1);
	tnew(cols, rows);
	wlinit(cols, rows);
	selinit();
	run();

	return 0;
}

