/* $OpenBSD: window-panes.c,v 1.4 2026/07/29 14:06:32 nicm Exp $ */

/*
 * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <stdlib.h>
#include <string.h>

#include "tmux.h"

static struct screen	*window_panes_init(struct window_mode_entry *,
			     struct cmdq_item *, struct cmd_find_state *,
			     struct args *);
static void		 window_panes_free(struct window_mode_entry *);
static void		 window_panes_resize(struct window_mode_entry *, u_int,
			     u_int);
static void		 window_panes_key(struct window_mode_entry *,
			     struct client *, struct session *,
			     struct winlink *, key_code, struct mouse_event *);

const struct window_mode window_panes_mode = {
	.name = "panes-mode",
	.flags = WINDOW_MODE_HIDE_PANE_STATUS|WINDOW_MODE_NO_STACK|
	    WINDOW_MODE_FILL_WINDOW|WINDOW_MODE_HIDE_SCROLLBARS,

	.init = window_panes_init,
	.free = window_panes_free,
	.resize = window_panes_resize,
	.key = window_panes_key,
};

struct window_panes_area {
	u_int	id;
	u_int	x;
	u_int	y;
	u_int	sx;
	u_int	sy;
};

struct window_panes_modedata {
	struct window_pane		*wp;
	struct session			*session;
	u_int				 source_session;
	u_int				 source_window;
	struct screen			 screen;
	struct screen			*preview;

	struct event			 timer;

	struct args_command_state	*state;
	u_int				 delay;
	int				 ignore_keys;
	int				 zoomed;

	struct window_panes_area	*areas;
	u_int				 areas_size;
};

#define WINDOW_PANES_BORDER_L 0x1
#define WINDOW_PANES_BORDER_R 0x2
#define WINDOW_PANES_BORDER_U 0x4
#define WINDOW_PANES_BORDER_D 0x8

static int
window_panes_get_source(struct window_panes_modedata *data, struct session **sp,
    struct winlink **wlp, struct window **wp)
{
	struct session	*s;
	struct window	*w;
	struct winlink	*wl = NULL;

	w = window_find_by_id(data->source_window);
	if (w == NULL)
		return (0);

	s = session_find_by_id(data->source_session);
	if (s != NULL)
		wl = winlink_find_by_window(&s->windows, w);
	if (wl == NULL)
		s = data->session;
	if (s != NULL)
		wl = winlink_find_by_window(&s->windows, w);

	if (sp != NULL)
		*sp = s;
	if (wlp != NULL)
		*wlp = wl;
	if (wp != NULL)
		*wp = w;
	return (1);
}

static void
window_panes_set_preview(struct window_panes_modedata *data)
{
	struct window_pane	*wp = data->wp;
	struct screen		*src = &wp->base, *dst;
	struct screen_write_ctx	 ctx;
	u_int			 sx = screen_size_x(src), sy = screen_size_y(src);

	data->preview = dst = xmalloc(sizeof *data->preview);
	screen_init(dst, sx, sy, 0);
	screen_write_start(&ctx, dst);
	screen_write_fast_copy(&ctx, src, 0, src->grid->hsize, sx, sy);
	screen_write_stop(&ctx);
	dst->mode = src->mode;
	dst->cx = src->cx;
	dst->cy = src->cy;
}

static void
window_panes_free_areas(struct window_panes_modedata *data)
{
	free(data->areas);
	data->areas = NULL;
	data->areas_size = 0;
}

static void
window_panes_add_area(struct window_panes_modedata *data,
    struct window_pane *wp, u_int x, u_int y, u_int sx, u_int sy)
{
	struct window_panes_area	*area;

	data->areas = xreallocarray(data->areas, data->areas_size + 1,
	    sizeof *data->areas);
	area = &data->areas[data->areas_size++];
	area->id = wp->id;
	area->x = x;
	area->y = y;
	area->sx = sx;
	area->sy = sy;
}

static int
window_panes_pane_floating(struct window_pane *wp)
{
	struct layout_cell	*lc = wp->saved_layout_cell;

	if (lc == NULL)
		lc = wp->layout_cell;
	if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
		return (0);
	return (1);
}

static int
window_panes_pane_visible(struct window_pane *wp)
{
	if (wp->saved_layout_cell != NULL)
		return (1);
	return (window_pane_is_visible(wp));
}

static int
window_panes_get_geometry(struct window_pane *wp, struct layout_cell *root,
    u_int osx, u_int osy, u_int dsx, u_int dsy, u_int *xp, u_int *yp,
    u_int *sxp, u_int *syp)
{
	struct layout_cell	*lc = wp->saved_layout_cell;
	int			 status;
	u_int			 x, y, sx, sy, x2, y2;

	if (lc == NULL)
		lc = wp->layout_cell;
	if (lc == NULL || osx == 0 || osy == 0 || dsx == 0 || dsy == 0)
		return (0);

	if (osx <= dsx && osy <= dsy) {
		x = lc->g.xoff;
		y = lc->g.yoff;
		x2 = x + lc->g.sx;
		y2 = y + lc->g.sy;
	} else {
		x = (u_int)lc->g.xoff * dsx / osx;
		y = (u_int)lc->g.yoff * dsy / osy;
		x2 = ((u_int)lc->g.xoff + lc->g.sx) * dsx / osx;
		y2 = ((u_int)lc->g.yoff + lc->g.sy) * dsy / osy;
	}

	if (x >= dsx || y >= dsy)
		return (0);
	if (x2 <= x)
		x2 = x + 1;
	if (y2 <= y)
		y2 = y + 1;
	if (x2 > dsx)
		x2 = dsx;
	if (y2 > dsy)
		y2 = dsy;

	sx = x2 - x;
	sy = y2 - y;
	if (sx == 0 || sy == 0)
		return (0);

	status = window_get_pane_status(wp->window);
	if (layout_add_horizontal_border(root, lc, status) && sy > 1) {
		if (status == PANE_STATUS_TOP)
			y++;
		sy--;
	}

	*xp = x;
	*yp = y;
	*sxp = sx;
	*syp = sy;
	return (1);
}

static void
window_panes_get_border_cell(struct window_panes_modedata *data,
    struct grid_cell *gc)
{
	struct format_tree	*ft;
	struct window_pane	*wp = data->wp;
	struct session		*s = data->session;

	memcpy(gc, &grid_default_cell, sizeof *gc);
	ft = format_create_defaults(NULL, NULL, s, s->curw, wp);
	style_apply(gc, wp->window->options, "display-panes-border-style", ft);
	format_free(ft);
}

static int
window_panes_map_x(u_int x, u_int osx, u_int dsx)
{
	if (osx <= dsx)
		return (x);
	return (x * dsx / osx);
}

static int
window_panes_map_y(u_int y, u_int osy, u_int dsy)
{
	if (osy <= dsy)
		return (y);
	return (y * dsy / osy);
}

static struct layout_cell *
window_panes_next_tiled_cell(struct layout_cell *lc)
{
	struct layout_cell	*next;

	next = TAILQ_NEXT(lc, entry);
	while (next != NULL) {
		if (~next->flags & LAYOUT_CELL_FLOATING)
			return (next);
		next = TAILQ_NEXT(next, entry);
	}
	return (NULL);
}

static void
window_panes_mark_border(u_char *map, u_int dsx, u_int dsy, u_int x, u_int y,
    u_char mask)
{
	if (x < dsx && y < dsy)
		map[y * dsx + x] |= mask;
}

static void
window_panes_mark_vline(u_char *map, u_int dsx, u_int dsy, int x, int y, int y2)
{
	u_char	mask;
	int	yy;

	if (x < 0 || (u_int)x >= dsx || y2 <= y)
		return;
	if (y < 0)
		y = 0;
	if ((u_int)y2 > dsy)
		y2 = dsy;

	for (yy = y; yy < y2; yy++) {
		mask = 0;
		if (yy > y)
			mask |= WINDOW_PANES_BORDER_U;
		if (yy + 1 < y2)
			mask |= WINDOW_PANES_BORDER_D;
		if (mask == 0)
			mask = WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D;
		window_panes_mark_border(map, dsx, dsy, x, yy, mask);
	}
}

static void
window_panes_mark_hline(u_char *map, u_int dsx, u_int dsy, int x, int x2, int y)
{
	u_char	mask;
	int	xx;

	if (y < 0 || (u_int)y >= dsy || x2 <= x)
		return;
	if (x < 0)
		x = 0;
	if ((u_int)x2 > dsx)
		x2 = dsx;

	for (xx = x; xx < x2; xx++) {
		mask = 0;
		if (xx > x)
			mask |= WINDOW_PANES_BORDER_L;
		if (xx + 1 < x2)
			mask |= WINDOW_PANES_BORDER_R;
		if (mask == 0)
			mask = WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R;
		window_panes_mark_border(map, dsx, dsy, xx, y, mask);
	}
}

static void
window_panes_mark_borders_cell(u_char *map, struct layout_cell *lc, u_int osx,
    u_int osy, u_int dsx, u_int dsy)
{
	struct layout_cell	*lcchild, *lcnext;
	int			 x, y, x2, y2;

	if (lc->type == LAYOUT_WINDOWPANE)
		return;

	TAILQ_FOREACH(lcchild, &lc->cells, entry) {
		window_panes_mark_borders_cell(map, lcchild, osx, osy, dsx,
		    dsy);
		if (lcchild->flags & LAYOUT_CELL_FLOATING)
			continue;
		lcnext = window_panes_next_tiled_cell(lcchild);
		if (lcnext == NULL)
			continue;

		if (lc->type == LAYOUT_LEFTRIGHT) {
			x = window_panes_map_x(lcchild->g.xoff + lcchild->g.sx,
			    osx, dsx);
			y = window_panes_map_y(lc->g.yoff, osy, dsy);
			y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy,
			    dsy);
			window_panes_mark_vline(map, dsx, dsy, x, y, y2);
		} else {
			x = window_panes_map_x(lc->g.xoff, osx, dsx);
			x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx,
			    dsx);
			y = window_panes_map_y(lcchild->g.yoff + lcchild->g.sy,
			    osy, dsy);
			window_panes_mark_hline(map, dsx, dsy, x, x2, y);
		}
	}
}

static void
window_panes_mark_pane_status_borders(u_char *map, struct window *w,
    struct layout_cell *root, u_int osx, u_int osy, u_int dsx, u_int dsy)
{
	struct window_pane	*wp;
	struct layout_cell	*lc;
	int			 status, x, y, x2, y2;

	status = window_get_pane_status(w);
	if (status != PANE_STATUS_TOP && status != PANE_STATUS_BOTTOM)
		return;

	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (!window_panes_pane_visible(wp))
			continue;

		lc = wp->saved_layout_cell;
		if (lc == NULL)
			lc = wp->layout_cell;
		if (lc == NULL ||
		    !layout_add_horizontal_border(root, lc, status))
			continue;

		x = window_panes_map_x(lc->g.xoff, osx, dsx);
		x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, dsx);
		if (status == PANE_STATUS_TOP)
			y = window_panes_map_y(lc->g.yoff, osy, dsy);
		else {
			y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy,
			    dsy);
			y = y2 - 1;
		}
		window_panes_mark_hline(map, dsx, dsy, x, x2, y);
	}
}

static int
window_panes_get_floating_borders(struct window_pane *wp, u_int osx, u_int osy,
    u_int dsx, u_int dsy, int *xp, int *yp, int *x2p, int *y2p)
{
	struct layout_cell	*lc;

	lc = wp->saved_layout_cell;
	if (lc == NULL)
		lc = wp->layout_cell;
	if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
		return (0);

	if (lc->g.xoff == 0)
		*xp = -1;
	else
		*xp = window_panes_map_x(lc->g.xoff - 1, osx, dsx);
	if (lc->g.yoff == 0)
		*yp = -1;
	else
		*yp = window_panes_map_y(lc->g.yoff - 1, osy, dsy);
	*x2p = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, dsx);
	*y2p = window_panes_map_y(lc->g.yoff + lc->g.sy, osy, dsy);
	return (1);
}

static int
window_panes_clip_floating_pane(struct window_pane *wp, u_int osx, u_int osy,
    u_int dsx, u_int dsy, u_int *xp, u_int *yp, u_int *sxp, u_int *syp)
{
	int	x, y, x2, y2, bx, by, bx2, by2;

	if (!window_panes_get_floating_borders(wp, osx, osy, dsx, dsy, &x, &y,
	    &x2, &y2))
		return (1);

	bx = *xp;
	by = *yp;
	bx2 = bx + *sxp - 1;
	by2 = by + *syp - 1;

	if (x >= 0 && bx <= x)
		bx = x + 1;
	if (y >= 0 && by <= y)
		by = y + 1;
	if ((u_int)x2 < dsx && bx2 >= x2)
		bx2 = x2 - 1;
	if ((u_int)y2 < dsy && by2 >= y2)
		by2 = y2 - 1;
	if (bx2 < bx || by2 < by)
		return (0);

	*xp = bx;
	*yp = by;
	*sxp = bx2 - bx + 1;
	*syp = by2 - by + 1;
	return (1);
}

static int
window_panes_border_cell_type(u_char mask)
{
	switch (mask) {
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R|
	    WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D:
		return (CELL_LRUD);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U:
		return (CELL_LRU);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_D:
		return (CELL_LRD);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R:
	case WINDOW_PANES_BORDER_L:
	case WINDOW_PANES_BORDER_R:
		return (CELL_LR);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D:
		return (CELL_ULD);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_U:
		return (CELL_LU);
	case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_D:
		return (CELL_LD);
	case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D:
		return (CELL_URD);
	case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U:
		return (CELL_RU);
	case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_D:
		return (CELL_RD);
	case WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D:
	case WINDOW_PANES_BORDER_U:
	case WINDOW_PANES_BORDER_D:
		return (CELL_UD);
	}
	return (CELL_NONE);
}

static int
window_panes_border_has_horizontal(u_char mask)
{
	return ((mask & (WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R)) != 0);
}

static int
window_panes_border_has_vertical(u_char mask)
{
	return ((mask & (WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D)) != 0);
}

static void
window_panes_mark_border_joins_cell(u_char *map, struct layout_cell *lc,
    u_int osx, u_int osy, u_int dsx, u_int dsy)
{
	struct layout_cell	*lcchild, *lcnext;
	int			 x, y, x2, y2;

	if (lc->type == LAYOUT_WINDOWPANE)
		return;

	TAILQ_FOREACH(lcchild, &lc->cells, entry) {
		window_panes_mark_border_joins_cell(map, lcchild, osx, osy,
		    dsx, dsy);
		if (lcchild->flags & LAYOUT_CELL_FLOATING)
			continue;
		lcnext = window_panes_next_tiled_cell(lcchild);
		if (lcnext == NULL)
			continue;

		if (lc->type == LAYOUT_LEFTRIGHT) {
			x = window_panes_map_x(lcchild->g.xoff + lcchild->g.sx,
			    osx, dsx);
			y = window_panes_map_y(lc->g.yoff, osy, dsy);
			y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy,
			    dsy);
			if (x < 0 || (u_int)x >= dsx)
				continue;
			if (y > 0 &&
			    window_panes_border_has_horizontal(
			    map[(y - 1) * dsx + x])) {
				window_panes_mark_border(map, dsx, dsy, x,
				    y - 1, WINDOW_PANES_BORDER_D);
				window_panes_mark_border(map, dsx, dsy, x, y,
				    WINDOW_PANES_BORDER_U);
			}
			if ((u_int)y2 < dsy &&
			    window_panes_border_has_horizontal(
			    map[y2 * dsx + x])) {
				window_panes_mark_border(map, dsx, dsy, x, y2,
				    WINDOW_PANES_BORDER_U);
				window_panes_mark_border(map, dsx, dsy, x,
				    y2 - 1, WINDOW_PANES_BORDER_D);
			}
		} else {
			x = window_panes_map_x(lc->g.xoff, osx, dsx);
			x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx,
			    dsx);
			y = window_panes_map_y(lcchild->g.yoff + lcchild->g.sy,
			    osy, dsy);
			if (y < 0 || (u_int)y >= dsy)
				continue;
			if (x > 0 &&
			    window_panes_border_has_vertical(
			    map[y * dsx + x - 1])) {
				window_panes_mark_border(map, dsx, dsy, x - 1,
				    y, WINDOW_PANES_BORDER_R);
				window_panes_mark_border(map, dsx, dsy, x, y,
				    WINDOW_PANES_BORDER_L);
			}
			if ((u_int)x2 < dsx &&
			    window_panes_border_has_vertical(
			    map[y * dsx + x2])) {
				window_panes_mark_border(map, dsx, dsy, x2, y,
				    WINDOW_PANES_BORDER_L);
				window_panes_mark_border(map, dsx, dsy, x2 - 1,
				    y, WINDOW_PANES_BORDER_R);
			}
		}
	}
}

static void
window_panes_draw_borders(struct screen_write_ctx *ctx, struct window *w,
    struct layout_cell *lc, const struct grid_cell *gc, u_int osx, u_int osy,
    u_int dsx, u_int dsy)
{
	struct grid_cell	 border_gc;
	u_char			*map;
	u_int			 xx, yy;
	int			 cell_type;

	if (dsx == 0 || dsy == 0)
		return;

	map = xcalloc(dsx, dsy);
	window_panes_mark_borders_cell(map, lc, osx, osy, dsx, dsy);
	window_panes_mark_pane_status_borders(map, w, lc, osx, osy, dsx,
	    dsy);
	window_panes_mark_border_joins_cell(map, lc, osx, osy, dsx, dsy);

	for (yy = 0; yy < dsy; yy++) {
		for (xx = 0; xx < dsx; xx++) {
			if (map[yy * dsx + xx] == 0)
				continue;
			cell_type = window_panes_border_cell_type(
			    map[yy * dsx + xx]);
			memcpy(&border_gc, gc, sizeof border_gc);
			border_gc.attr |= GRID_ATTR_CHARSET;
			utf8_set(&border_gc.data, CELL_BORDERS[cell_type]);
			screen_write_cursormove(ctx, xx, yy, 0);
			screen_write_cell(ctx, &border_gc);
		}
	}
	free(map);
}

static void
window_panes_draw_floating_border(struct screen_write_ctx *ctx,
    struct window_pane *wp, const struct grid_cell *gc, u_int osx, u_int osy,
    u_int dsx, u_int dsy)
{
	struct grid_cell	 border_gc;
	u_char			*map;
	u_int			 xx, yy;
	int			 x, y, x2, y2, cell_type;

	if (dsx == 0 || dsy == 0)
		return;
	if (!window_panes_get_floating_borders(wp, osx, osy, dsx, dsy, &x, &y,
	    &x2, &y2))
		return;

	map = xcalloc(dsx, dsy);
	window_panes_mark_hline(map, dsx, dsy, x, x2 + 1, y);
	window_panes_mark_hline(map, dsx, dsy, x, x2 + 1, y2);
	window_panes_mark_vline(map, dsx, dsy, x, y, y2 + 1);
	window_panes_mark_vline(map, dsx, dsy, x2, y, y2 + 1);

	for (yy = 0; yy < dsy; yy++) {
		for (xx = 0; xx < dsx; xx++) {
			if (map[yy * dsx + xx] == 0)
				continue;
			cell_type = window_panes_border_cell_type(
			    map[yy * dsx + xx]);
			memcpy(&border_gc, gc, sizeof border_gc);
			border_gc.attr |= GRID_ATTR_CHARSET;
			utf8_set(&border_gc.data, CELL_BORDERS[cell_type]);
			screen_write_cursormove(ctx, xx, yy, 0);
			screen_write_cell(ctx, &border_gc);
		}
	}
	free(map);
}

static void
window_panes_clear_floating_area(struct screen_write_ctx *ctx,
    struct window_pane *wp, u_int osx, u_int osy, u_int dsx, u_int dsy)
{
	struct grid_cell	gc;
	int			x, y, x2, y2, xx, yy;

	if (!window_panes_get_floating_borders(wp, osx, osy, dsx, dsy, &x, &y,
	    &x2, &y2))
		return;

	memcpy(&gc, &grid_default_cell, sizeof gc);
	if (x < 0)
		x = 0;
	if (y < 0)
		y = 0;
	if ((u_int)x2 >= dsx)
		x2 = dsx - 1;
	if ((u_int)y2 >= dsy)
		y2 = dsy - 1;
	if (x2 < x || y2 < y)
		return;

	for (yy = y; yy <= y2; yy++) {
		screen_write_cursormove(ctx, x, yy, 0);
		for (xx = x; xx <= x2; xx++)
			screen_write_putc(ctx, &gc, ' ');
	}
}

static void
window_panes_draw_format(struct window_panes_modedata *data,
    struct screen_write_ctx *ctx, struct window_pane *wp, u_int x, u_int y,
    u_int sx, const struct grid_cell *gc)
{
	struct session	*s = data->session;
	struct winlink	*wl = s->curw;
	const char	*format;
	char		*expanded;

	if (sx == 0)
		return;
	format = options_get_string(data->wp->window->options,
	    "display-panes-format");
	if (*format == '\0')
		return;

	window_panes_get_source(data, &s, &wl, NULL);
	if (s == NULL)
		return;

	expanded = format_single(NULL, format, NULL, s, wl, wp);
	if (*expanded != '\0') {
		screen_write_cursormove(ctx, x, y, 0);
		format_draw(ctx, gc, sx, expanded, NULL, 0);
	}
	free(expanded);
}

static void
window_panes_draw_number(struct window_panes_modedata *data,
    struct screen_write_ctx *ctx, struct window_pane *wp, u_int pane, u_int x,
    u_int y, u_int sx, u_int sy)
{
	struct session		*s = data->session;
	struct window		*w = wp->window;
	struct winlink		*wl = s->curw;
	struct options		*oo = data->wp->window->options;
	struct grid_cell	 fgc, bgc;
	struct format_tree	*ft;
	const char		*name;
	char			 buf[16], lbuf[16] = { 0 }, *ptr;
	size_t			 len, llen = 0, width;
	u_int			 cx, cy, px, py, idx, i, j, format;

	len = xsnprintf(buf, sizeof buf, "%u", pane);
	if (pane > 9 && pane < 35)
		llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10));
	if (sx < len)
		return;

	window_panes_get_source(data, &s, &wl, NULL);
	if (s != NULL) {
		if (wl == NULL)
			wl = s->curw;
	}
	if (w->active == wp)
		name = "display-panes-active-colour";
	else
		name = "display-panes-colour";
	ft = format_create_defaults(NULL, NULL, s, wl, wp);
	style_apply(&fgc, oo, name, ft);
	format_free(ft);

	memcpy(&bgc, &grid_default_cell, sizeof bgc);
	bgc.bg = fgc.fg;

	format = 0;
	if (options_get_string(oo, "display-panes-format")[0] != '\0')
		format = 1;
	width = (len * 6) - 1;
	if (sx < width || sy < (format ? 7 : 5)) {
		width = len;
		if (llen != 0 && sx >= len + llen + 1)
			width += llen + 1;
		cx = x + (sx - width) / 2;
		cy = y + sy / 2;
		screen_write_cursormove(ctx, cx, cy, 0);
		screen_write_puts(ctx, &fgc, "%s", buf);
		if (width > len)
			screen_write_puts(ctx, &fgc, " %s", lbuf);
		if (format && sy > 1)
			window_panes_draw_format(data, ctx, wp, x, y, sx, &fgc);
		return;
	}

	px = (sx - width) / 2;
	py = (sy - 5) / 2;
	for (ptr = buf; *ptr != '\0'; ptr++) {
		if (*ptr < '0' || *ptr > '9')
			continue;
		idx = *ptr - '0';

		for (j = 0; j < 5; j++) {
			for (i = 0; i < 5; i++) {
				if (!window_clock_table[idx][j][i])
					continue;
				screen_write_cursormove(ctx, x + px + i,
				    y + py + j, 0);
				screen_write_putc(ctx, &bgc, ' ');
			}
		}
		px += 6;
	}

	if (sy <= 6)
		return;
	window_panes_draw_format(data, ctx, wp, x, y, sx, &fgc);
	if (llen != 0) {
		cx = x + px - llen - 1;
		cy = y + py + 5;
		screen_write_cursormove(ctx, cx, cy, 0);
		screen_write_puts(ctx, &fgc, "%s", lbuf);
	}
}

static void
window_panes_draw_pane(struct window_panes_modedata *data,
    struct screen_write_ctx *ctx, struct window_pane *wp,
    struct layout_cell *root, u_int osx, u_int osy, u_int dsx, u_int dsy)
{
	struct screen		*s = &wp->base;
	u_int			 pane, x, y, sx, sy;

	if (!window_panes_pane_visible(wp))
		return;
	if (!window_panes_get_geometry(wp, root, osx, osy, dsx, dsy, &x, &y,
	    &sx, &sy))
		return;
	if (!window_panes_clip_floating_pane(wp, osx, osy, dsx, dsy, &x, &y,
	    &sx, &sy))
		return;
	if (window_pane_index(wp, &pane) != 0)
		return;
	window_panes_add_area(data, wp, x, y, sx, sy);

	screen_write_cursormove(ctx, x, y, 0);
	if (data->preview != NULL &&
	    wp == data->wp &&
	    sx <= screen_size_x(data->preview) &&
	    sy <= screen_size_y(data->preview))
		s = data->preview;
	if (osx <= dsx && osy <= dsy)
		screen_write_fast_copy(ctx, s, 0, s->grid->hsize, sx, sy);
	else
		screen_write_preview(ctx, s, sx, sy);
	window_panes_draw_number(data, ctx, wp, pane, x, y, sx, sy);
}

static void
window_panes_draw_screen(struct window_mode_entry *wme)
{
	struct window_panes_modedata	*data = wme->data;
	struct window			*w;
	struct window_pane		*wp;
	struct screen_write_ctx		 ctx;
	struct layout_cell		*root;
	struct grid_cell		 border_gc;
	u_int				 osx, osy, sx, sy;

	if (!window_panes_get_source(data, NULL, NULL, &w))
		return;
	root = w->saved_layout_root;
	if (root == NULL)
		root = w->layout_root;
	if (root == NULL)
		return;

	osx = root->g.sx;
	osy = root->g.sy;
	sx = screen_size_x(&data->screen);
	sy = screen_size_y(&data->screen);

	window_panes_free_areas(data);
	screen_write_start(&ctx, &data->screen);
	screen_write_clearscreen(&ctx, 8);
	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (window_panes_pane_floating(wp))
			continue;
		window_panes_draw_pane(data, &ctx, wp, root, osx, osy, sx, sy);
	}
	window_panes_get_border_cell(data, &border_gc);
	window_panes_draw_borders(&ctx, w, root, &border_gc, osx, osy, sx, sy);
	TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) {
		if (!window_panes_pane_floating(wp))
			continue;
		window_panes_clear_floating_area(&ctx, wp, osx, osy, sx, sy);
		window_panes_draw_pane(data, &ctx, wp, root, osx, osy, sx, sy);
		window_panes_draw_floating_border(&ctx, wp, &border_gc, osx,
		    osy, sx, sy);
	}
	screen_write_stop(&ctx);

	data->wp->flags |= PANE_REDRAW;
}

static void
window_panes_timer_callback(__unused int fd, __unused short events, void *arg)
{
	struct window_mode_entry	*wme = arg;

	window_pane_reset_mode(wme->wp);
}

static struct screen *
window_panes_init(struct window_mode_entry *wme, struct cmdq_item *item,
    __unused struct cmd_find_state *fs, struct args *args)
{
	struct window_pane		*wp = wme->wp;
	struct window			*w = wp->window;
	struct window_panes_modedata	*data;
	struct cmd			*self;
	struct cmd_find_state		*source, *target;
	struct session			*s;
	struct timeval			 tv;
	char				*cause;
	u_int				 sx = screen_size_x(&wp->base);
	u_int				 sy = screen_size_y(&wp->base);
	u_int				 delay;

	if (item == NULL)
		return (NULL);
	self = cmdq_get_cmd(item);
	if (self == NULL)
		return (NULL);

	source = cmdq_get_source(item);
	target = cmdq_get_target(item);
	s = target->s;

	if (!args_has(args, 'd'))
		delay = options_get_number(w->options, "display-panes-time");
	else {
		delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause);
		if (cause != NULL) {
			cmdq_error(item, "delay %s", cause);
			free(cause);
			return (NULL);
		}
	}

	wme->data = data = xcalloc(1, sizeof *data);
	data->wp = wp;
	data->session = s;

	screen_init(&data->screen, sx, sy, 0);
	data->screen.mode &= ~MODE_CURSOR;

	data->state = args_make_commands_prepare(self, item, 0,
	    "select-pane -t \"%%%\"", 0, 0);
	if (args_has(args, 's')) {
		data->source_session = source->s->id;
		data->source_window = source->w->id;
	} else {
		data->source_session = target->s->id;
		data->source_window = target->w->id;
	}

	data->delay = delay;
	data->ignore_keys = args_has(args, 'N');

	if (args_has(args, 'Z'))
		data->zoomed = -1;
	else {
		data->zoomed = (w->flags & WINDOW_ZOOMED);
		if (!data->zoomed)
			window_panes_set_preview(data);
		if (!data->zoomed && window_zoom(wp) == 0)
			server_redraw_window(w);
	}

	evtimer_set(&data->timer, window_panes_timer_callback, wme);
	if (data->delay != 0) {
		tv.tv_sec = data->delay / 1000;
		tv.tv_usec = (data->delay % 1000) * 1000;
		evtimer_add(&data->timer, &tv);
	}

	window_panes_draw_screen(wme);
	return (&data->screen);
}

static void
window_panes_free(struct window_mode_entry *wme)
{
	struct window_panes_modedata	*data = wme->data;
	struct window			*w = wme->wp->window;

	evtimer_del(&data->timer);

	if (data->zoomed == 0)
		server_unzoom_window(w);
	server_redraw_window(w);
	server_redraw_window_borders(w);
	server_status_window(w);

	if (data->state != NULL)
		args_make_commands_free(data->state);
	window_panes_free_areas(data);
	if (data->preview != NULL) {
		screen_free(data->preview);
		free(data->preview);
	}
	screen_free(&data->screen);
	free(data);
}

static void
window_panes_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
{
	struct window_panes_modedata	*data = wme->data;

	screen_resize(&data->screen, sx, sy, 0);
	window_panes_draw_screen(wme);
}

static void
window_panes_run_command(struct window_panes_modedata *data, struct client *c,
	struct window_pane *wp)
{
	struct cmdq_item	*new_item;
	struct cmd_list		*cmdlist;
	char			*expanded, *error;

	xasprintf(&expanded, "%%%u", wp->id);
	cmdlist = args_make_commands(data->state, 1, &expanded, &error);
	if (cmdlist == NULL) {
		cmdq_append(c, cmdq_get_error(error));
		free(error);
	} else {
		new_item = cmdq_get_command(cmdlist, NULL);
		cmdq_append(c, new_item);
	}
	free(expanded);
}

static struct window_pane *
window_panes_find_pane(struct window_panes_modedata *data, u_int x, u_int y)
{
	struct window_panes_area	*area;
	u_int			 i;

	for (i = data->areas_size; i > 0; i--) {
		area = &data->areas[i - 1];
		if (x < area->x || x >= area->x + area->sx)
			continue;
		if (y < area->y || y >= area->y + area->sy)
			continue;
		return (window_pane_find_by_id(area->id));
	}
	return (NULL);
}

static struct window_pane *
window_panes_key_pane(struct window_panes_modedata *data, key_code key)
{
	struct window		*w;
	u_int			 index;

	if (key >= '0' && key <= '9')
		index = key - '0';
	else if ((key & KEYC_MASK_MODIFIERS) == 0) {
		key &= KEYC_MASK_KEY;
		if (key < 'a' || key > 'z')
			return (NULL);
		index = 10 + (key - 'a');
	} else
		return (NULL);
	if (!window_panes_get_source(data, NULL, NULL, &w))
		return (NULL);
	return (window_pane_at_index(w, index));
}

static struct window_pane *
window_panes_get_target(struct window_mode_entry *wme, key_code key,
    struct mouse_event *m)
{
	struct window_panes_modedata	*data = wme->data;
	u_int				 x, y;

	if (data->ignore_keys)
		return (NULL);

	if (KEYC_IS_MOUSE(key)) {
		if (key != KEYC_MOUSEDOWN1_PANE ||
		    m == NULL ||
		    cmd_mouse_at(wme->wp, m, &x, &y, 0) != 0)
			return (NULL);
		return (window_panes_find_pane(data, x, y));
	}

	return (window_panes_key_pane(data, key));
}

static void
window_panes_key(struct window_mode_entry *wme, struct client *c,
    __unused struct session *s, __unused struct winlink *wl, key_code key,
    struct mouse_event *m)
{
	struct window_pane		*wp = wme->wp, *target = NULL;
	struct window_panes_modedata	*data = wme->data;

	if (key == '\033' || key == 'q') {
		window_pane_reset_mode(wp);
		return;
	}

	target = window_panes_get_target(wme, key, m);
	if (target == NULL) {
		if (!data->ignore_keys && !KEYC_IS_MOUSE(key))
			window_pane_reset_mode(wp);
		return;
	}

	if (wp->window->flags & WINDOW_ZOOMED)
		window_unzoom(wp->window, 1);
	window_panes_run_command(data, c, target);
	window_pane_reset_mode(wp);
}
