mirror of
https://github.com/tmux/tmux.git
synced 2026-09-19 11:28:08 +00:00
Compare commits
20 Commits
redraw-dam
...
kitty-keys
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c96fde851 | ||
|
|
1abf9f7023 | ||
|
|
cb80f01460 | ||
|
|
e2334c3205 | ||
|
|
9e88490a05 | ||
|
|
10ea823da6 | ||
|
|
75cb580e36 | ||
|
|
e880cf63e0 | ||
|
|
325b40e8c4 | ||
|
|
f95363aa83 | ||
|
|
f8fc53b6f8 | ||
|
|
d215280b00 | ||
|
|
d859a0f410 | ||
|
|
10d00cc7cb | ||
|
|
14a01dd337 | ||
|
|
a958883625 | ||
|
|
d202aa7ac1 | ||
|
|
d9a5e82678 | ||
|
|
13c10f672c | ||
|
|
b7c5030004 |
14
CHANGES
14
CHANGES
@@ -111,6 +111,18 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
||||
|
||||
* Add support for the Kitty keyboard protocol. When extended-keys is on,
|
||||
tmux uses it with terminals which support it, detected when a client
|
||||
attaches or set with a new kittykeys terminal feature, and standard
|
||||
extended keys or VT10x keys with other terminals, so terminals with
|
||||
different capabilities may be attached at the same time. Programs inside
|
||||
tmux may ask for either the Kitty protocol or modifyOtherKeys and get keys
|
||||
in the form they asked for. The Kitty protocol can report the Super and
|
||||
Hyper modifiers (key name prefixes s- and H-).
|
||||
|
||||
* Add a client_key_mode format giving the extended key reporting mode in
|
||||
use for a client, alongside the existing pane_key_mode for a pane.
|
||||
|
||||
* Allow individual terminal features to be disabled by adding @ to their names
|
||||
in terminal-features, and add a utf8 feature for terminals which support
|
||||
UTF-8 output.
|
||||
@@ -130,6 +142,8 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Add capture-pane -I to include the time each line entered history.
|
||||
|
||||
* Various memory leak fixes from Heon Jeong.
|
||||
|
||||
* Add a default C-b T binding to change the current pane title.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
|
||||
@@ -171,6 +171,7 @@ dist_tmux_SOURCES = \
|
||||
grid.c \
|
||||
hooks.c \
|
||||
hyperlinks.c \
|
||||
input-kitty.c \
|
||||
input-keys.c \
|
||||
input.c \
|
||||
job.c \
|
||||
@@ -212,6 +213,7 @@ dist_tmux_SOURCES = \
|
||||
tty-acs.c \
|
||||
tty-draw.c \
|
||||
tty-features.c \
|
||||
tty-kitty.c \
|
||||
tty-keys.c \
|
||||
tty-term.c \
|
||||
tty.c \
|
||||
|
||||
@@ -91,10 +91,8 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
||||
wp = target.wp;
|
||||
|
||||
if (wl != NULL) {
|
||||
if (wp != NULL) {
|
||||
window_redraw_active_switch(wp->window, wp);
|
||||
if (wp != NULL)
|
||||
window_set_active_pane(wp->window, wp, 1);
|
||||
}
|
||||
session_set_current(s, wl);
|
||||
if (wp != NULL)
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
|
||||
@@ -300,7 +300,6 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
int y, ly, x, lx;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -322,17 +321,10 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
ly = m->statusat - 1;
|
||||
|
||||
if (x != lx || y != ly) {
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = wp->sx;
|
||||
old_sy = wp->sy;
|
||||
|
||||
lc->g.xoff += x - lx;
|
||||
lc->g.yoff += y - ly;
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||
int left, right;
|
||||
int new_xoff, new_yoff, resizes = 0;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -249,10 +248,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
lc = wp->layout_cell;
|
||||
sx = wp->sx;
|
||||
sy = wp->sy;
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = (int)wp->sx;
|
||||
old_sy = (int)wp->sy;
|
||||
left = wp->xoff - 1;
|
||||
right = wp->xoff + sx;
|
||||
if (window_pane_scrollbar_reserve(wp) &&
|
||||
@@ -352,8 +347,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
}
|
||||
if (resizes != 0) {
|
||||
layout_fix_panes(w, NULL);
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
|
||||
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
@@ -219,6 +219,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||
if (args_has(args, 'D') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||
|
||||
style = args_get(args, 's');
|
||||
if (style != NULL) {
|
||||
@@ -360,7 +362,6 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
enum pane_lines lines;
|
||||
u_int sx, sy;
|
||||
int x, y, xoff, yoff, border;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
if (c->tty.mouse_last_pane == -1)
|
||||
return;
|
||||
@@ -416,15 +417,8 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
if (sy < PANE_MINIMUM)
|
||||
sy = PANE_MINIMUM;
|
||||
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = wp->sx;
|
||||
old_sy = wp->sy;
|
||||
|
||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
|
||||
45
format.c
45
format.c
@@ -1617,6 +1617,41 @@ format_cb_client_height(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for client_key_mode. */
|
||||
static void *
|
||||
format_cb_client_key_mode(struct format_tree *ft)
|
||||
{
|
||||
struct client *c = ft->c;
|
||||
const char *mode;
|
||||
char *s;
|
||||
int kitty, eks;
|
||||
|
||||
if (c == NULL || (~c->tty.flags & TTY_STARTED))
|
||||
return (NULL);
|
||||
|
||||
if (c->tty.flags & TTY_KKBPUSHED) {
|
||||
xasprintf(&s, "Kitty %u", c->tty.kitty_keys);
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* This mirrors the choice made in tty_update_features(). */
|
||||
kitty = ((c->tty.term->flags & TERM_KITTYKEYS) != 0);
|
||||
eks = (options_get_number(global_options, "extended-keys") != 0);
|
||||
if (kitty)
|
||||
eks = 0;
|
||||
if (eks && tty_term_has(c->tty.term, TTYC_ENEKS))
|
||||
mode = "Ext";
|
||||
else
|
||||
mode = "VT10x";
|
||||
|
||||
/* The terminal supports Kitty keys but they are not in use. */
|
||||
if (kitty) {
|
||||
xasprintf(&s, "%s (Kitty)", mode);
|
||||
return (s);
|
||||
}
|
||||
return (xstrdup(mode));
|
||||
}
|
||||
|
||||
/* Callback for client_key_table. */
|
||||
static void *
|
||||
format_cb_client_key_table(struct format_tree *ft)
|
||||
@@ -2487,7 +2522,14 @@ format_cb_pane_unseen_changes(struct format_tree *ft)
|
||||
static void *
|
||||
format_cb_pane_key_mode(struct format_tree *ft)
|
||||
{
|
||||
char *s;
|
||||
|
||||
if (ft->wp != NULL && ft->wp->screen != NULL) {
|
||||
if (ft->wp->screen->kitty_keys.flags != 0) {
|
||||
xasprintf(&s, "Kitty %u",
|
||||
ft->wp->screen->kitty_keys.flags);
|
||||
return (s);
|
||||
}
|
||||
switch (ft->wp->screen->mode & EXTENDED_KEY_MODES) {
|
||||
case MODE_KEYS_EXTENDED:
|
||||
return (xstrdup("Ext 1"));
|
||||
@@ -3642,6 +3684,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "client_height", FORMAT_TABLE_STRING,
|
||||
format_cb_client_height
|
||||
},
|
||||
{ "client_key_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_client_key_mode
|
||||
},
|
||||
{ "client_key_table", FORMAT_TABLE_STRING,
|
||||
format_cb_client_key_table
|
||||
},
|
||||
|
||||
11
input-keys.c
11
input-keys.c
@@ -464,7 +464,8 @@ input_key_extended(struct bufferevent *bev, key_code key)
|
||||
} else
|
||||
key &= KEYC_MASK_KEY;
|
||||
|
||||
if (options_get_number(global_options, "extended-keys-format") == 1)
|
||||
if (options_get_number(global_options, "extended-keys-format") ==
|
||||
EXTENDED_KEYS_XTERM)
|
||||
xsnprintf(tmp, sizeof tmp, "\033[27;%c;%llu~", modifier, key);
|
||||
else
|
||||
xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", key, modifier);
|
||||
@@ -588,6 +589,10 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Kitty keys take precedence if the application asked for them. */
|
||||
if (input_key_kitty(s, bev, key) == 0)
|
||||
return (0);
|
||||
|
||||
/* Is this backspace? */
|
||||
if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) {
|
||||
newkey = options_get_number(global_options, "backspace");
|
||||
@@ -677,6 +682,10 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
|
||||
log_debug("%s: ignoring key 0x%llx", __func__, key);
|
||||
return (0);
|
||||
}
|
||||
if (key & (KEYC_SUPER|KEYC_HYPER))
|
||||
return (input_key_vt10x(bev, key));
|
||||
if (s->kitty_keys.flags & KITTY_KEY_SUPPORTED)
|
||||
return (input_key_vt10x(bev, key));
|
||||
|
||||
/*
|
||||
* No builtin key sequence; construct an extended key sequence
|
||||
|
||||
313
input-kitty.c
Normal file
313
input-kitty.c
Normal file
@@ -0,0 +1,313 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
||||
*
|
||||
* 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 <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Track pane keyboard state and encode keys for applications. */
|
||||
|
||||
struct input_kitty_key {
|
||||
key_code key;
|
||||
u_int number;
|
||||
char final;
|
||||
};
|
||||
|
||||
static const struct input_kitty_key input_kitty_keys[] = {
|
||||
{ C0_ESC, 27, 'u' },
|
||||
{ C0_CR, 13, 'u' },
|
||||
{ C0_HT, 9, 'u' },
|
||||
{ KEYC_BSPACE, 127, 'u' },
|
||||
{ KEYC_IC, 2, '~' },
|
||||
{ KEYC_DC, 3, '~' },
|
||||
{ KEYC_LEFT, 1, 'D' },
|
||||
{ KEYC_RIGHT, 1, 'C' },
|
||||
{ KEYC_UP, 1, 'A' },
|
||||
{ KEYC_DOWN, 1, 'B' },
|
||||
{ KEYC_PPAGE, 5, '~' },
|
||||
{ KEYC_NPAGE, 6, '~' },
|
||||
{ KEYC_HOME, 1, 'H' },
|
||||
{ KEYC_END, 1, 'F' },
|
||||
{ KEYC_F1, 1, 'P' },
|
||||
{ KEYC_F2, 1, 'Q' },
|
||||
{ KEYC_F3, 13, '~' },
|
||||
{ KEYC_F4, 1, 'S' },
|
||||
{ KEYC_F5, 15, '~' },
|
||||
{ KEYC_F6, 17, '~' },
|
||||
{ KEYC_F7, 18, '~' },
|
||||
{ KEYC_F8, 19, '~' },
|
||||
{ KEYC_F9, 20, '~' },
|
||||
{ KEYC_F10, 21, '~' },
|
||||
{ KEYC_F11, 23, '~' },
|
||||
{ KEYC_F12, 24, '~' },
|
||||
{ KEYC_CAPSLOCK, 57358, 'u' },
|
||||
{ KEYC_SCROLLLOCK, 57359, 'u' },
|
||||
{ KEYC_NUMLOCK, 57360, 'u' },
|
||||
{ KEYC_PRINTSCREEN, 57361, 'u' },
|
||||
{ KEYC_PAUSE, 57362, 'u' },
|
||||
{ KEYC_MENU, 57363, 'u' },
|
||||
{ KEYC_F13, 57376, 'u' },
|
||||
{ KEYC_F14, 57377, 'u' },
|
||||
{ KEYC_F15, 57378, 'u' },
|
||||
{ KEYC_F16, 57379, 'u' },
|
||||
{ KEYC_F17, 57380, 'u' },
|
||||
{ KEYC_F18, 57381, 'u' },
|
||||
{ KEYC_F19, 57382, 'u' },
|
||||
{ KEYC_F20, 57383, 'u' },
|
||||
{ KEYC_F21, 57384, 'u' },
|
||||
{ KEYC_F22, 57385, 'u' },
|
||||
{ KEYC_F23, 57386, 'u' },
|
||||
{ KEYC_F24, 57387, 'u' },
|
||||
{ KEYC_F25, 57388, 'u' },
|
||||
{ KEYC_F26, 57389, 'u' },
|
||||
{ KEYC_F27, 57390, 'u' },
|
||||
{ KEYC_F28, 57391, 'u' },
|
||||
{ KEYC_F29, 57392, 'u' },
|
||||
{ KEYC_F30, 57393, 'u' },
|
||||
{ KEYC_F31, 57394, 'u' },
|
||||
{ KEYC_F32, 57395, 'u' },
|
||||
{ KEYC_F33, 57396, 'u' },
|
||||
{ KEYC_F34, 57397, 'u' },
|
||||
{ KEYC_F35, 57398, 'u' },
|
||||
{ KEYC_KP_ZERO|KEYC_KEYPAD, 57399, 'u' },
|
||||
{ KEYC_KP_ONE|KEYC_KEYPAD, 57400, 'u' },
|
||||
{ KEYC_KP_TWO|KEYC_KEYPAD, 57401, 'u' },
|
||||
{ KEYC_KP_THREE|KEYC_KEYPAD, 57402, 'u' },
|
||||
{ KEYC_KP_FOUR|KEYC_KEYPAD, 57403, 'u' },
|
||||
{ KEYC_KP_FIVE|KEYC_KEYPAD, 57404, 'u' },
|
||||
{ KEYC_KP_SIX|KEYC_KEYPAD, 57405, 'u' },
|
||||
{ KEYC_KP_SEVEN|KEYC_KEYPAD, 57406, 'u' },
|
||||
{ KEYC_KP_EIGHT|KEYC_KEYPAD, 57407, 'u' },
|
||||
{ KEYC_KP_NINE|KEYC_KEYPAD, 57408, 'u' },
|
||||
{ KEYC_KP_PERIOD|KEYC_KEYPAD, 57409, 'u' },
|
||||
{ KEYC_KP_SLASH|KEYC_KEYPAD, 57410, 'u' },
|
||||
{ KEYC_KP_STAR|KEYC_KEYPAD, 57411, 'u' },
|
||||
{ KEYC_KP_MINUS|KEYC_KEYPAD, 57412, 'u' },
|
||||
{ KEYC_KP_PLUS|KEYC_KEYPAD, 57413, 'u' },
|
||||
{ KEYC_KP_ENTER|KEYC_KEYPAD, 57414, 'u' },
|
||||
{ KEYC_KP_EQUAL|KEYC_KEYPAD, 57415, 'u' },
|
||||
{ KEYC_KP_SEPARATOR|KEYC_KEYPAD, 57416, 'u' },
|
||||
{ KEYC_KP_LEFT|KEYC_KEYPAD, 57417, 'u' },
|
||||
{ KEYC_KP_RIGHT|KEYC_KEYPAD, 57418, 'u' },
|
||||
{ KEYC_KP_UP|KEYC_KEYPAD, 57419, 'u' },
|
||||
{ KEYC_KP_DOWN|KEYC_KEYPAD, 57420, 'u' },
|
||||
{ KEYC_KP_PPAGE|KEYC_KEYPAD, 57421, 'u' },
|
||||
{ KEYC_KP_NPAGE|KEYC_KEYPAD, 57422, 'u' },
|
||||
{ KEYC_KP_HOME|KEYC_KEYPAD, 57423, 'u' },
|
||||
{ KEYC_KP_END|KEYC_KEYPAD, 57424, 'u' },
|
||||
{ KEYC_KP_IC|KEYC_KEYPAD, 57425, 'u' },
|
||||
{ KEYC_KP_DC|KEYC_KEYPAD, 57426, 'u' },
|
||||
{ KEYC_KP_BEGIN|KEYC_KEYPAD, 57427, '~' },
|
||||
{ KEYC_MEDIA_PLAY, 57428, 'u' },
|
||||
{ KEYC_MEDIA_PAUSE, 57429, 'u' },
|
||||
{ KEYC_MEDIA_PLAYPAUSE, 57430, 'u' },
|
||||
{ KEYC_MEDIA_REVERSE, 57431, 'u' },
|
||||
{ KEYC_MEDIA_STOP, 57432, 'u' },
|
||||
{ KEYC_MEDIA_FASTFORWARD, 57433, 'u' },
|
||||
{ KEYC_MEDIA_REWIND, 57434, 'u' },
|
||||
{ KEYC_MEDIA_NEXT, 57435, 'u' },
|
||||
{ KEYC_MEDIA_PREVIOUS, 57436, 'u' },
|
||||
{ KEYC_MEDIA_RECORD, 57437, 'u' },
|
||||
{ KEYC_VOLUME_DOWN, 57438, 'u' },
|
||||
{ KEYC_VOLUME_UP, 57439, 'u' },
|
||||
{ KEYC_VOLUME_MUTE, 57440, 'u' },
|
||||
{ KEYC_LEFT_SHIFT, 57441, 'u' },
|
||||
{ KEYC_LEFT_CTRL, 57442, 'u' },
|
||||
{ KEYC_LEFT_ALT, 57443, 'u' },
|
||||
{ KEYC_LEFT_SUPER, 57444, 'u' },
|
||||
{ KEYC_LEFT_HYPER, 57445, 'u' },
|
||||
{ KEYC_LEFT_META, 57446, 'u' },
|
||||
{ KEYC_RIGHT_SHIFT, 57447, 'u' },
|
||||
{ KEYC_RIGHT_CTRL, 57448, 'u' },
|
||||
{ KEYC_RIGHT_ALT, 57449, 'u' },
|
||||
{ KEYC_RIGHT_SUPER, 57450, 'u' },
|
||||
{ KEYC_RIGHT_HYPER, 57451, 'u' },
|
||||
{ KEYC_RIGHT_META, 57452, 'u' },
|
||||
{ KEYC_ISO_LEVEL3_SHIFT, 57453, 'u' },
|
||||
{ KEYC_ISO_LEVEL5_SHIFT, 57454, 'u' }
|
||||
};
|
||||
|
||||
void
|
||||
input_kitty_reset(struct screen *s)
|
||||
{
|
||||
memset(&s->kitty_keys, 0, sizeof s->kitty_keys);
|
||||
}
|
||||
|
||||
void
|
||||
input_kitty_alternate_on(struct screen *s)
|
||||
{
|
||||
s->saved_kitty_keys = s->kitty_keys;
|
||||
input_kitty_reset(s);
|
||||
}
|
||||
|
||||
void
|
||||
input_kitty_alternate_off(struct screen *s)
|
||||
{
|
||||
s->kitty_keys = s->saved_kitty_keys;
|
||||
memset(&s->saved_kitty_keys, 0, sizeof s->saved_kitty_keys);
|
||||
}
|
||||
|
||||
void
|
||||
input_kitty_set(struct screen *s, u_int flags, int mode)
|
||||
{
|
||||
flags &= KITTY_KEY_SUPPORTED;
|
||||
if (mode == 2)
|
||||
s->kitty_keys.flags |= flags;
|
||||
else if (mode == 3)
|
||||
s->kitty_keys.flags &= ~flags;
|
||||
else
|
||||
s->kitty_keys.flags = flags;
|
||||
}
|
||||
|
||||
void
|
||||
input_kitty_push(struct screen *s, u_int flags)
|
||||
{
|
||||
s->kitty_keys.saved_flags = s->kitty_keys.flags;
|
||||
s->kitty_keys.have_saved = 1;
|
||||
s->kitty_keys.flags = flags & KITTY_KEY_SUPPORTED;
|
||||
}
|
||||
|
||||
void
|
||||
input_kitty_pop(struct screen *s, u_int count)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
if (s->kitty_keys.have_saved && count == 1)
|
||||
s->kitty_keys.flags = s->kitty_keys.saved_flags;
|
||||
else
|
||||
s->kitty_keys.flags = 0;
|
||||
s->kitty_keys.have_saved = 0;
|
||||
}
|
||||
|
||||
static u_int
|
||||
input_kitty_modifiers(key_code key)
|
||||
{
|
||||
u_int modifiers = 1;
|
||||
|
||||
if (key & KEYC_SHIFT)
|
||||
modifiers += 1;
|
||||
if (key & KEYC_META)
|
||||
modifiers += 2;
|
||||
if (key & KEYC_CTRL)
|
||||
modifiers += 4;
|
||||
if (key & KEYC_SUPER)
|
||||
modifiers += 8;
|
||||
if (key & KEYC_HYPER)
|
||||
modifiers += 16;
|
||||
return (modifiers);
|
||||
}
|
||||
|
||||
static const struct input_kitty_key *
|
||||
input_kitty_lookup(key_code key)
|
||||
{
|
||||
key_code lookup;
|
||||
u_int i;
|
||||
|
||||
lookup = key & (KEYC_MASK_KEY|KEYC_KEYPAD);
|
||||
for (i = 0; i < nitems(input_kitty_keys); i++) {
|
||||
if (input_kitty_keys[i].key == lookup)
|
||||
return (&input_kitty_keys[i]);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
int
|
||||
input_key_kitty(struct screen *s, struct bufferevent *bev, key_code key)
|
||||
{
|
||||
const struct input_kitty_key *ikk;
|
||||
struct utf8_data ud;
|
||||
wchar_t wc;
|
||||
key_code modifiers, onlykey;
|
||||
u_int flags, number, modifier;
|
||||
char tmp[64];
|
||||
size_t size;
|
||||
|
||||
flags = s->kitty_keys.flags & KITTY_KEY_SUPPORTED;
|
||||
if (flags == 0)
|
||||
return (-1);
|
||||
if ((key & KEYC_MASK_KEY) == KEYC_BTAB)
|
||||
key = C0_HT|(key & ~KEYC_MASK_KEY)|KEYC_SHIFT;
|
||||
|
||||
modifiers = key & (KEYC_SHIFT|KEYC_META|KEYC_CTRL|KEYC_SUPER|KEYC_HYPER);
|
||||
ikk = input_kitty_lookup(key);
|
||||
if (ikk != NULL) {
|
||||
if ((flags & KITTY_KEY_REPORT_ALL) == 0) {
|
||||
if (ikk->key == C0_CR || ikk->key == C0_HT ||
|
||||
ikk->key == KEYC_BSPACE)
|
||||
return (-1);
|
||||
if (ikk->final != 'u' &&
|
||||
(modifiers & (KEYC_SUPER|KEYC_HYPER)) == 0)
|
||||
return (-1);
|
||||
}
|
||||
number = ikk->number;
|
||||
modifier = input_kitty_modifiers(key);
|
||||
if (ikk->final == 'u') {
|
||||
if (modifier == 1)
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%uu", number);
|
||||
else
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%uu",
|
||||
number, modifier);
|
||||
} else if (ikk->final == '~') {
|
||||
if (modifier == 1)
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%u~", number);
|
||||
else
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%u~",
|
||||
number, modifier);
|
||||
} else {
|
||||
if (modifier == 1)
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%c",
|
||||
ikk->final);
|
||||
else
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[1;%u%c",
|
||||
modifier, ikk->final);
|
||||
}
|
||||
goto write;
|
||||
}
|
||||
|
||||
if ((flags & KITTY_KEY_REPORT_ALL) == 0 &&
|
||||
(modifiers & (KEYC_META|KEYC_CTRL|KEYC_SUPER|KEYC_HYPER)) == 0)
|
||||
return (-1);
|
||||
|
||||
onlykey = key & KEYC_MASK_KEY;
|
||||
if (KEYC_IS_UNICODE(key)) {
|
||||
utf8_to_data(onlykey, &ud);
|
||||
if (utf8_towc(&ud, &wc) != UTF8_DONE)
|
||||
return (-1);
|
||||
number = wc;
|
||||
} else if (onlykey <= 0x7f)
|
||||
number = onlykey;
|
||||
else
|
||||
return (-1);
|
||||
if ((key & KEYC_SHIFT) && number >= 'A' && number <= 'Z')
|
||||
number += 'a' - 'A';
|
||||
|
||||
modifier = input_kitty_modifiers(key);
|
||||
if (modifier == 1)
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%uu", number);
|
||||
else
|
||||
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%uu", number,
|
||||
modifier);
|
||||
|
||||
write:
|
||||
log_debug("%s: %.*s", __func__, (int)size, tmp);
|
||||
bufferevent_write(bev, tmp, size);
|
||||
return (0);
|
||||
}
|
||||
37
input.c
37
input.c
@@ -279,6 +279,10 @@ enum input_csi_type {
|
||||
INPUT_CSI_HPA,
|
||||
INPUT_CSI_ICH,
|
||||
INPUT_CSI_IL,
|
||||
INPUT_CSI_KITTY_POP,
|
||||
INPUT_CSI_KITTY_PUSH,
|
||||
INPUT_CSI_KITTY_QUERY,
|
||||
INPUT_CSI_KITTY_SET,
|
||||
INPUT_CSI_MODOFF,
|
||||
INPUT_CSI_MODSET,
|
||||
INPUT_CSI_QUERY,
|
||||
@@ -344,7 +348,11 @@ static const struct input_table_entry input_csi_table[] = {
|
||||
{ 'r', "", INPUT_CSI_DECSTBM },
|
||||
{ 's', "", INPUT_CSI_SCP },
|
||||
{ 't', "", INPUT_CSI_WINOPS },
|
||||
{ 'u', "", INPUT_CSI_RCP }
|
||||
{ 'u', "", INPUT_CSI_RCP },
|
||||
{ 'u', "<", INPUT_CSI_KITTY_POP },
|
||||
{ 'u', "=", INPUT_CSI_KITTY_SET },
|
||||
{ 'u', ">", INPUT_CSI_KITTY_PUSH },
|
||||
{ 'u', "?", INPUT_CSI_KITTY_QUERY }
|
||||
};
|
||||
|
||||
/* Input transition. */
|
||||
@@ -1557,6 +1565,33 @@ input_csi_dispatch(struct input_ctx *ictx)
|
||||
if (options_get_number(global_options, "extended-keys") == 2)
|
||||
screen_write_mode_set(sctx, MODE_KEYS_EXTENDED);
|
||||
break;
|
||||
case INPUT_CSI_KITTY_QUERY:
|
||||
if (options_get_number(global_options, "extended-keys") == 0)
|
||||
break;
|
||||
input_reply(ictx, 1, "\033[?%uu", s->kitty_keys.flags);
|
||||
break;
|
||||
case INPUT_CSI_KITTY_SET:
|
||||
if (options_get_number(global_options, "extended-keys") == 0)
|
||||
break;
|
||||
n = input_get(ictx, 0, 0, 0);
|
||||
m = input_get(ictx, 1, 1, 1);
|
||||
if (n >= 0 && m >= 1 && m <= 3)
|
||||
input_kitty_set(s, n, m);
|
||||
break;
|
||||
case INPUT_CSI_KITTY_PUSH:
|
||||
if (options_get_number(global_options, "extended-keys") == 0)
|
||||
break;
|
||||
n = input_get(ictx, 0, 0, 0);
|
||||
if (n >= 0)
|
||||
input_kitty_push(s, n);
|
||||
break;
|
||||
case INPUT_CSI_KITTY_POP:
|
||||
if (options_get_number(global_options, "extended-keys") == 0)
|
||||
break;
|
||||
n = input_get(ictx, 0, 1, 1);
|
||||
if (n >= 0)
|
||||
input_kitty_pop(s, n);
|
||||
break;
|
||||
case INPUT_CSI_WINOPS:
|
||||
input_csi_dispatch_winops(ictx);
|
||||
break;
|
||||
|
||||
82
key-string.c
82
key-string.c
@@ -45,6 +45,29 @@ static const struct {
|
||||
{ "F10", KEYC_F10|KEYC_IMPLIED_META },
|
||||
{ "F11", KEYC_F11|KEYC_IMPLIED_META },
|
||||
{ "F12", KEYC_F12|KEYC_IMPLIED_META },
|
||||
{ "F13", KEYC_F13|KEYC_IMPLIED_META },
|
||||
{ "F14", KEYC_F14|KEYC_IMPLIED_META },
|
||||
{ "F15", KEYC_F15|KEYC_IMPLIED_META },
|
||||
{ "F16", KEYC_F16|KEYC_IMPLIED_META },
|
||||
{ "F17", KEYC_F17|KEYC_IMPLIED_META },
|
||||
{ "F18", KEYC_F18|KEYC_IMPLIED_META },
|
||||
{ "F19", KEYC_F19|KEYC_IMPLIED_META },
|
||||
{ "F20", KEYC_F20|KEYC_IMPLIED_META },
|
||||
{ "F21", KEYC_F21|KEYC_IMPLIED_META },
|
||||
{ "F22", KEYC_F22|KEYC_IMPLIED_META },
|
||||
{ "F23", KEYC_F23|KEYC_IMPLIED_META },
|
||||
{ "F24", KEYC_F24|KEYC_IMPLIED_META },
|
||||
{ "F25", KEYC_F25|KEYC_IMPLIED_META },
|
||||
{ "F26", KEYC_F26|KEYC_IMPLIED_META },
|
||||
{ "F27", KEYC_F27|KEYC_IMPLIED_META },
|
||||
{ "F28", KEYC_F28|KEYC_IMPLIED_META },
|
||||
{ "F29", KEYC_F29|KEYC_IMPLIED_META },
|
||||
{ "F30", KEYC_F30|KEYC_IMPLIED_META },
|
||||
{ "F31", KEYC_F31|KEYC_IMPLIED_META },
|
||||
{ "F32", KEYC_F32|KEYC_IMPLIED_META },
|
||||
{ "F33", KEYC_F33|KEYC_IMPLIED_META },
|
||||
{ "F34", KEYC_F34|KEYC_IMPLIED_META },
|
||||
{ "F35", KEYC_F35|KEYC_IMPLIED_META },
|
||||
{ "IC", KEYC_IC|KEYC_IMPLIED_META },
|
||||
{ "Insert", KEYC_IC|KEYC_IMPLIED_META },
|
||||
{ "DC", KEYC_DC|KEYC_IMPLIED_META },
|
||||
@@ -122,6 +145,54 @@ static const struct {
|
||||
{ "KPEnter", KEYC_KP_ENTER|KEYC_KEYPAD },
|
||||
{ "KP0", KEYC_KP_ZERO|KEYC_KEYPAD },
|
||||
{ "KP.", KEYC_KP_PERIOD|KEYC_KEYPAD },
|
||||
{ "KP=", KEYC_KP_EQUAL|KEYC_KEYPAD },
|
||||
{ "KP,", KEYC_KP_SEPARATOR|KEYC_KEYPAD },
|
||||
{ "KPLeft", KEYC_KP_LEFT|KEYC_KEYPAD },
|
||||
{ "KPRight", KEYC_KP_RIGHT|KEYC_KEYPAD },
|
||||
{ "KPUp", KEYC_KP_UP|KEYC_KEYPAD },
|
||||
{ "KPDown", KEYC_KP_DOWN|KEYC_KEYPAD },
|
||||
{ "KPPageUp", KEYC_KP_PPAGE|KEYC_KEYPAD },
|
||||
{ "KPPageDown", KEYC_KP_NPAGE|KEYC_KEYPAD },
|
||||
{ "KPHome", KEYC_KP_HOME|KEYC_KEYPAD },
|
||||
{ "KPEnd", KEYC_KP_END|KEYC_KEYPAD },
|
||||
{ "KPInsert", KEYC_KP_IC|KEYC_KEYPAD },
|
||||
{ "KPDelete", KEYC_KP_DC|KEYC_KEYPAD },
|
||||
{ "KPBegin", KEYC_KP_BEGIN|KEYC_KEYPAD },
|
||||
|
||||
/* Other function keys. */
|
||||
{ "CapsLock", KEYC_CAPSLOCK },
|
||||
{ "ScrollLock", KEYC_SCROLLLOCK },
|
||||
{ "NumLock", KEYC_NUMLOCK },
|
||||
{ "PrintScreen", KEYC_PRINTSCREEN },
|
||||
{ "Pause", KEYC_PAUSE },
|
||||
{ "Menu", KEYC_MENU },
|
||||
{ "MediaPlay", KEYC_MEDIA_PLAY },
|
||||
{ "MediaPause", KEYC_MEDIA_PAUSE },
|
||||
{ "MediaPlayPause", KEYC_MEDIA_PLAYPAUSE },
|
||||
{ "MediaReverse", KEYC_MEDIA_REVERSE },
|
||||
{ "MediaStop", KEYC_MEDIA_STOP },
|
||||
{ "MediaFastForward", KEYC_MEDIA_FASTFORWARD },
|
||||
{ "MediaRewind", KEYC_MEDIA_REWIND },
|
||||
{ "MediaNext", KEYC_MEDIA_NEXT },
|
||||
{ "MediaPrevious", KEYC_MEDIA_PREVIOUS },
|
||||
{ "MediaRecord", KEYC_MEDIA_RECORD },
|
||||
{ "VolumeDown", KEYC_VOLUME_DOWN },
|
||||
{ "VolumeUp", KEYC_VOLUME_UP },
|
||||
{ "VolumeMute", KEYC_VOLUME_MUTE },
|
||||
{ "LeftShift", KEYC_LEFT_SHIFT },
|
||||
{ "LeftControl", KEYC_LEFT_CTRL },
|
||||
{ "LeftAlt", KEYC_LEFT_ALT },
|
||||
{ "LeftSuper", KEYC_LEFT_SUPER },
|
||||
{ "LeftHyper", KEYC_LEFT_HYPER },
|
||||
{ "LeftMeta", KEYC_LEFT_META },
|
||||
{ "RightShift", KEYC_RIGHT_SHIFT },
|
||||
{ "RightControl", KEYC_RIGHT_CTRL },
|
||||
{ "RightAlt", KEYC_RIGHT_ALT },
|
||||
{ "RightSuper", KEYC_RIGHT_SUPER },
|
||||
{ "RightHyper", KEYC_RIGHT_HYPER },
|
||||
{ "RightMeta", KEYC_RIGHT_META },
|
||||
{ "ISOLevel3Shift", KEYC_ISO_LEVEL3_SHIFT },
|
||||
{ "ISOLevel5Shift", KEYC_ISO_LEVEL5_SHIFT },
|
||||
|
||||
/* Mouse keys. */
|
||||
KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1),
|
||||
@@ -226,9 +297,14 @@ key_string_get_modifiers(const char **string)
|
||||
modifiers |= KEYC_META;
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
modifiers |= KEYC_SHIFT;
|
||||
break;
|
||||
case 's':
|
||||
modifiers |= KEYC_SUPER;
|
||||
break;
|
||||
case 'H':
|
||||
modifiers |= KEYC_HYPER;
|
||||
break;
|
||||
default:
|
||||
*string = NULL;
|
||||
return (0);
|
||||
@@ -349,6 +425,10 @@ key_string_lookup_key(key_code key, int with_flags)
|
||||
strlcat(out, "M-", sizeof out);
|
||||
if (key & KEYC_SHIFT)
|
||||
strlcat(out, "S-", sizeof out);
|
||||
if (key & KEYC_SUPER)
|
||||
strlcat(out, "s-", sizeof out);
|
||||
if (key & KEYC_HYPER)
|
||||
strlcat(out, "H-", sizeof out);
|
||||
key &= KEYC_MASK_KEY;
|
||||
|
||||
/* Handle no key. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout-custom.c,v 1.40 2026/09/09 07:03:39 nicm Exp $ */
|
||||
/* $OpenBSD: layout-custom.c,v 1.41 2026/09/09 09:01:19 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -28,8 +28,8 @@
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
* Layouts can be represented as strings in a JSON format (v2). The legacy
|
||||
* format (v1) will be removed in the future and should no longer be used.
|
||||
* Layouts can be represented as strings in a JSON format (v2). The version 1
|
||||
* format will be removed in the future and should no longer be used.
|
||||
*
|
||||
* The current (v2) format is JSON. The top level has two keys:
|
||||
* "V": version number, currently 2
|
||||
@@ -57,6 +57,9 @@
|
||||
* "z": z-index, if a floating pane
|
||||
*/
|
||||
|
||||
/* Maximum nesting depth for version 1 layouts. */
|
||||
#define LAYOUT_V1_MAX_DEPTH 1000
|
||||
|
||||
/* Layout string. */
|
||||
struct layout_string {
|
||||
char *dat;
|
||||
@@ -362,7 +365,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Append information for a single cell in the legacy (v1) format. */
|
||||
/* Append information for a single cell in the version 1 format. */
|
||||
static int
|
||||
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
|
||||
{
|
||||
@@ -778,7 +781,7 @@ layout_assign(struct window *w, struct layout_parse_ctx *pctx)
|
||||
layout_assign_fallback(w, w->layout_root);
|
||||
}
|
||||
|
||||
/* Construct a cell from the legacy (v1) format. */
|
||||
/* Construct a cell from the version 1 format. */
|
||||
static struct layout_cell *
|
||||
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
||||
{
|
||||
@@ -827,12 +830,15 @@ layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
||||
return (lc);
|
||||
}
|
||||
|
||||
/* Construct a layout from the legacy (v1) format. */
|
||||
/* Construct a layout from the version 1 format. */
|
||||
static struct layout_cell *
|
||||
layout_construct_v1(struct layout_cell *lcparent, const char **layout)
|
||||
layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
|
||||
{
|
||||
struct layout_cell *lc, *lcchild;
|
||||
|
||||
if (depth > LAYOUT_V1_MAX_DEPTH)
|
||||
return (NULL);
|
||||
|
||||
lc = layout_construct_cell(lcparent, layout);
|
||||
if (lc == NULL)
|
||||
return (NULL);
|
||||
@@ -855,7 +861,7 @@ layout_construct_v1(struct layout_cell *lcparent, const char **layout)
|
||||
|
||||
do {
|
||||
(*layout)++;
|
||||
lcchild = layout_construct_v1(lc, layout);
|
||||
lcchild = layout_construct_v1(lc, layout, depth + 1);
|
||||
if (lcchild == NULL)
|
||||
goto fail;
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
|
||||
@@ -1068,7 +1074,8 @@ layout_construct(const char *input, struct layout_parse_ctx *pctx)
|
||||
*pctx->cause = xstrdup("invalid layout checksum");
|
||||
return (-1);
|
||||
}
|
||||
if ((pctx->root = layout_construct_v1(NULL, &input)) == NULL) {
|
||||
pctx->root = layout_construct_v1(NULL, &input, 0);
|
||||
if (pctx->root == NULL) {
|
||||
*pctx->cause = xstrdup("invalid layout");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
57
layout.c
57
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 nicm Exp $ */
|
||||
/* $OpenBSD: layout.c,v 1.100 2026/09/11 08:16:14 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -469,6 +469,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
sx = PANE_MINIMUM;
|
||||
else
|
||||
sx = sx - sb_w - sb_pad;
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
|
||||
window_pane_resize(wp, sx, sy);
|
||||
@@ -476,11 +477,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
if (wp->xoff != old_xoff ||
|
||||
wp->yoff != old_yoff ||
|
||||
wp->sx != old_sx ||
|
||||
wp->sy != old_sy) {
|
||||
wp->sy != old_sy)
|
||||
changed = 1;
|
||||
if (window_pane_scrollbar_reserve(wp))
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
redraw_invalidate_scene(w);
|
||||
@@ -770,6 +768,49 @@ layout_free(struct window *w, int only_nodes)
|
||||
layout_free_cell(w->layout_root, only_nodes);
|
||||
}
|
||||
|
||||
/* Move and resize floating panes so they stay inside the window. */
|
||||
static void
|
||||
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
u_int pad, avail, csx, csy;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
||||
lc = wp->layout_cell;
|
||||
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
|
||||
continue;
|
||||
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
||||
pad = 0;
|
||||
else
|
||||
pad = 1;
|
||||
|
||||
csx = lc->g.sx;
|
||||
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
|
||||
if (csx > avail)
|
||||
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||
csy = lc->g.sy;
|
||||
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
|
||||
if (csy > avail)
|
||||
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||
if (csx != lc->g.sx || csy != lc->g.sy)
|
||||
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
|
||||
|
||||
if (lc->g.xoff + lc->g.sx + pad > sx) {
|
||||
if (lc->g.sx + 2 * pad >= sx)
|
||||
lc->g.xoff = pad;
|
||||
else
|
||||
lc->g.xoff = sx - lc->g.sx - pad;
|
||||
}
|
||||
if (lc->g.yoff + lc->g.sy + pad > sy) {
|
||||
if (lc->g.sy + 2 * pad >= sy)
|
||||
lc->g.yoff = pad;
|
||||
else
|
||||
lc->g.yoff = sy - lc->g.sy - pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize the entire layout after window resize. */
|
||||
void
|
||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
@@ -790,8 +831,11 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
* out proportionately - this should leave the layout fitting the new
|
||||
* window size.
|
||||
*/
|
||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
|
||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
|
||||
layout_clamp_floating_panes(w, sx, sy);
|
||||
layout_fix_panes(w, NULL);
|
||||
return;
|
||||
}
|
||||
xchange = sx - lc->g.sx;
|
||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||
if (xchange < 0 && xchange < -xlimit)
|
||||
@@ -821,6 +865,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_clamp_floating_panes(w, sx, sy);
|
||||
layout_fix_panes(w, NULL);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.246 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -76,7 +76,8 @@ static const char *options_table_pane_border_indicators_list[] = {
|
||||
"off", "colour", "arrows", "both", NULL
|
||||
};
|
||||
static const char *options_table_pane_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none",
|
||||
"rounded", NULL
|
||||
};
|
||||
static const char *options_table_popup_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||
@@ -91,7 +92,7 @@ static const char *options_table_window_size_list[] = {
|
||||
"largest", "smallest", "manual", "latest", NULL
|
||||
};
|
||||
static const char *options_table_remain_on_exit_list[] = {
|
||||
"off", "on", "failed", "key", NULL
|
||||
"off", "on", "failed", "key", "failed-key", NULL
|
||||
};
|
||||
static const char *options_table_destroy_unattached_list[] = {
|
||||
"off", "on", "keep-last", "keep-group", NULL
|
||||
@@ -103,7 +104,7 @@ static const char *options_table_extended_keys_list[] = {
|
||||
"off", "on", "always", NULL
|
||||
};
|
||||
static const char *options_table_extended_keys_format_list[] = {
|
||||
"csi-u", "xterm", NULL
|
||||
"csi-u", "xterm", "kitty", NULL
|
||||
};
|
||||
static const char *options_table_allow_passthrough_list[] = {
|
||||
"off", "on", "all", NULL
|
||||
@@ -1698,7 +1699,8 @@ const struct options_table_entry options_table[] = {
|
||||
.choices = options_table_remain_on_exit_list,
|
||||
.default_num = 0,
|
||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||
"pressed ('key') or be automatically killed ('off' or "
|
||||
"pressed after any exit ('key') or after a failure "
|
||||
"('failed-key'), or be automatically killed ('off' or "
|
||||
"'failed') when the program inside exits."
|
||||
},
|
||||
|
||||
|
||||
@@ -1400,6 +1400,12 @@ options_push_changes(const char *name)
|
||||
TAILQ_FOREACH(loop, &clients, entry)
|
||||
server_client_set_key_table(loop, NULL);
|
||||
}
|
||||
if (strcmp(name, "extended-keys") == 0) {
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
if (loop->tty.flags & TTY_STARTED)
|
||||
tty_update_features(&loop->tty);
|
||||
}
|
||||
}
|
||||
if (strcmp(name, "user-keys") == 0) {
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
if (loop->tty.flags & TTY_OPENED)
|
||||
|
||||
60
popup.c
60
popup.c
@@ -136,8 +136,7 @@ popup_reapply_styles(struct popup_data *pd)
|
||||
}
|
||||
|
||||
static void
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx, __unused u_int py,
|
||||
__unused u_int ny)
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
{
|
||||
struct popup_data *pd = ttyctx->arg;
|
||||
|
||||
@@ -322,60 +321,11 @@ popup_resize_cb(__unused struct client *c, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for a popup's rectangle, given in raw client/tty
|
||||
* coordinates. Status-line cells are outside the window scene - they have no
|
||||
* corresponding window content and redraw_damage_window() can't reach them -
|
||||
* so if the popup's rectangle overlaps the status line, force it to redraw
|
||||
* separately. The rest of the rectangle is clipped to the actual pane area
|
||||
* (above or below the status line, whichever side it's on) before being
|
||||
* translated into window coordinates and reported the normal way, the same
|
||||
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
||||
* cmd-split-window.c).
|
||||
*/
|
||||
static void
|
||||
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
||||
{
|
||||
struct window *w;
|
||||
int statusat;
|
||||
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
||||
|
||||
if (c->session == NULL)
|
||||
return;
|
||||
w = c->session->curw->window;
|
||||
|
||||
lines = status_line_size(c);
|
||||
statusat = status_at_line(c);
|
||||
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
||||
py + sy > (u_int)statusat)
|
||||
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
||||
|
||||
if (statusat == 0) {
|
||||
top = lines;
|
||||
bottom = c->tty.sy;
|
||||
} else if (statusat > 0) {
|
||||
top = 0;
|
||||
bottom = statusat;
|
||||
} else {
|
||||
top = 0;
|
||||
bottom = c->tty.sy;
|
||||
}
|
||||
y0 = (py > top) ? py : top;
|
||||
y1 = (py + sy < bottom) ? py + sy : bottom;
|
||||
if (y0 >= y1)
|
||||
return;
|
||||
|
||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
||||
}
|
||||
|
||||
static void
|
||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
struct mouse_event *m)
|
||||
{
|
||||
u_int px, py;
|
||||
u_int old_px = pd->px, old_py = pd->py;
|
||||
u_int old_sx = pd->sx, old_sy = pd->sy;
|
||||
|
||||
if (!MOUSE_DRAG(m->b))
|
||||
pd->dragging = OFF;
|
||||
@@ -398,9 +348,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
pd->dy = m->y - pd->py;
|
||||
pd->ppx = px;
|
||||
pd->ppy = py;
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
server_redraw_client(c);
|
||||
} else if (pd->dragging == SIZE) {
|
||||
if (pd->border_lines == BOX_LINES_NONE) {
|
||||
if (m->x < pd->px + 1)
|
||||
@@ -427,9 +375,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
if (pd->job != NULL)
|
||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||
}
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
server_redraw_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Selecting a pane with attach-session from an already attached client must
|
||||
# redraw pane contents when window-style and window-active-style differ.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
||||
BEFORE=$DIR/before
|
||||
AFTER=$DIR/after
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
||||
"printf 'LEFT'; exec sleep 100") || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g default-terminal screen || exit 1
|
||||
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
||||
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
||||
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
||||
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
sleep 1
|
||||
$OUTER send-keys -t outer:0.0 x || exit 1
|
||||
sleep 1
|
||||
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
||||
fail "attach-session did not select the target pane"
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
||||
|
||||
# A forced redraw produces the correct scene. It must be identical to the
|
||||
# scene drawn immediately by attach-session.
|
||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||
sleep 1
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
||||
cmp -s "$BEFORE" "$AFTER" ||
|
||||
fail "attach-session left stale active/inactive pane styles"
|
||||
|
||||
exit 0
|
||||
@@ -30,8 +30,11 @@ killw
|
||||
EOF
|
||||
sleep 1
|
||||
$TMUX has || exit 1
|
||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||
cat <<EOF|cmp -s $TMP - || exit 1
|
||||
# Use a control client to request legacy layouts, keeping only pane lines
|
||||
# from the control protocol output.
|
||||
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
||||
grep '^%[0-9]' >$TMP || exit 1
|
||||
cat <<EOF|cmp $TMP - || exit 1
|
||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||
# "floating" case in screen-redraw.c, which draws that frame at
|
||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||
# content area). Damage scoped to only the content area left the frame's
|
||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||
# of un-erased border frames behind - visible as several "corners" stacked
|
||||
# up rather than just the pane's current one.
|
||||
#
|
||||
# This bug has nothing to do with images - it reproduces with a plain
|
||||
# floating pane and no image support required.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
#
|
||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||
# 1-based positions to the outer pane holding the inner client - this
|
||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||
# move or resize a floating pane by its border.
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||
# before dragging anything.
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||
|
||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||
# its width) down several rows in a few separate steps, then release. A
|
||||
# single drag() call already does press/motion/release, so call it several
|
||||
# times in a row to simulate a multi-step real drag.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
STARTROW=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((STARTROW + i + 1))
|
||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# Exactly one top-left corner should remain - the pane's current position.
|
||||
# This is expected to fail before the fix: multiple corners (a trail of
|
||||
# un-erased frames) would remain from the intermediate drag positions.
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||
|
||||
exit 0
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: dragging a floating pane across another pane's ordinary
|
||||
# content must not redraw that other pane's scrollbar, unless the drag
|
||||
# actually crosses the scrollbar's own strip.
|
||||
#
|
||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||
# floating pane's old or new rectangle, rather than just its narrow
|
||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||
# colour and counting how many times its SGR code appears in the client's
|
||||
# raw output while the floating pane is dragged vertically over that pane's
|
||||
# body, well clear of its scrollbar column: with the fix, it should never
|
||||
# reappear after the initial draw.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on || fail "set mouse failed"
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||
|
||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||
|
||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||
|
||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||
fail "set pane-scrollbars-style failed"
|
||||
|
||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||
|
||||
# A small floating pane placed well inside the left pane's content area,
|
||||
# clear of its (right-hand) scrollbar column by several columns.
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||
fail "sanity: floating pane too close to the scrollbar column"
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Drag the floating pane straight up and down by its top border, staying at
|
||||
# a fixed column the whole time - this never crosses the left pane's
|
||||
# scrollbar strip, only its ordinary content.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
row=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row + 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row - 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# The scrollbar colour should never reappear - its geometry never changed,
|
||||
# and the drag never crossed its column. This is expected to fail before
|
||||
# the fix - see the header comment.
|
||||
n=$(grep -ac '48;5;201' $TMP)
|
||||
[ "$n" -eq 0 ] ||
|
||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||
|
||||
exit 0
|
||||
@@ -268,5 +268,149 @@ $TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
||||
#
|
||||
# layout_resize clamps floating panes back inside the window when it shrinks:
|
||||
# move them and, only if they cannot fit, shrink them (never below
|
||||
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
||||
# single-line border the clamp counts 1 cell of border per side, exactly as
|
||||
# layout_floating_args_parse does on creation; with no border it counts 0.
|
||||
# Each case below gets its own window, since resize-window fixes a window at
|
||||
# a manual size for the rest of its life.
|
||||
|
||||
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
||||
# takes the early-return path in layout_resize, added during PR #5582's
|
||||
# review round, since there is no tiled tree to walk.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for lone float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
||||
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
||||
# window's right and bottom edges, so the float fits before it is shrunk.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for lone float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
||||
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
||||
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
|
||||
# Case 5: grow the window back. The clamp must not chase the window back
|
||||
# outward -- it only ever pulls a float in, never restores where it was.
|
||||
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
||||
fail "resize-window grow (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
||||
# the window's root cell stays tiled and layout_resize takes the normal path
|
||||
# (the clamp call after layout_fix_offsets) instead of the early return
|
||||
# above. Same geometry as case 1, so the same numbers should come out.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for tiled sibling failed"
|
||||
$TMUX split-window -t "$win" 'sleep 100' ||
|
||||
fail "split-window for tiled sibling failed"
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for tiled sibling failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (tiled sibling) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
||||
# body -- also the normal path, via the tiled base pane new-window creates.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for new-pane repro failed"
|
||||
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
||||
fail "resize-window to 120x40 failed"
|
||||
|
||||
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
||||
fail "new-pane for new-pane repro failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
||||
|
||||
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (new-pane repro) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 4: a float that already fits inside the shrunk window is left alone --
|
||||
# assert position and size are both unchanged, not just one of them.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for untouched float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
||||
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
||||
fail "new-pane for untouched float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (untouched float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
||||
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
||||
# in the clamp).
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for pad=0 float failed"
|
||||
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
||||
fail "set pane-border-lines none failed"
|
||||
|
||||
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
||||
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
||||
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for pad=0 float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
||||
|
||||
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (pad=0 float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Resizing a floating pane must refresh session status formats which depend on
|
||||
# its geometry, not only the pane scene and borders.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has_status()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client status did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||
'sleep 100') || fail "could not create floating pane"
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||
$INNER set-option -g status-right '' || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||
|
||||
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
X=$((RIGHT + 2))
|
||||
Y=$((TOP + 2))
|
||||
|
||||
# Grab the right frame and enlarge the floating pane.
|
||||
mouse 0 "$X" "$Y" M
|
||||
mouse 32 "$((X + 1))" "$Y" M
|
||||
mouse 32 "$((X + 8))" "$Y" M
|
||||
mouse 0 "$((X + 8))" "$Y" m
|
||||
|
||||
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||
|
||||
exit 0
|
||||
146
regress/kitty-keys-clients.sh
Normal file
146
regress/kitty-keys-clients.sh
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test per-client keyboard protocol detection and pane-side encoding.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
KCONF=$(mktemp)
|
||||
LCONF=$(mktemp)
|
||||
OUT=$(mktemp)
|
||||
TMP=$(mktemp)
|
||||
SOCKETS="testKmc$$ testKka$$ testKua$$ testKrt$$ testKru$$ testKlq$$ testKlu$$"
|
||||
|
||||
printf '%s\n' 'set -g extended-keys on' >"$KCONF"
|
||||
printf '%s\n' 'set -g extended-keys off' >"$LCONF"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
rm -f "$KCONF" "$LCONF" "$OUT" "$TMP"
|
||||
for socket in $SOCKETS; do
|
||||
$TEST_TMUX -L"$socket" kill-server 2>/dev/null
|
||||
done
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_mode()
|
||||
{
|
||||
tmux=$1
|
||||
wanted=$2
|
||||
i=0
|
||||
while [ "$($tmux display-message -pt: '#{pane_key_mode}')" != "$wanted" ] &&
|
||||
[ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$($tmux display-message -pt: '#{pane_key_mode}')" = "$wanted" ]
|
||||
}
|
||||
|
||||
wait_for_output()
|
||||
{
|
||||
i=0
|
||||
while [ ! -s "$1" ] && [ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -s "$1" ]
|
||||
}
|
||||
|
||||
client_mode()
|
||||
{
|
||||
tmux=$1
|
||||
session=$2
|
||||
|
||||
if [ -z "$session" ]; then
|
||||
$tmux list-clients -F '#{client_key_mode}'
|
||||
else
|
||||
$tmux list-clients -F '#{client_session}:#{client_key_mode}' |
|
||||
sed -n "s/^$session://p"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_client_mode()
|
||||
{
|
||||
tmux=$1
|
||||
session=$2
|
||||
wanted=$3
|
||||
i=0
|
||||
while [ "$(client_mode "$tmux" "$session")" != "$wanted" ] &&
|
||||
[ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(client_mode "$tmux" "$session")" = "$wanted" ]
|
||||
}
|
||||
|
||||
# Each client must be negotiated independently. A tmux with extended-keys off
|
||||
# stands in for a terminal without Kitty keys, since it does not answer the
|
||||
# query.
|
||||
M="$TEST_TMUX -LtestKmc$$ -f$KCONF"
|
||||
KA="$TEST_TMUX -LtestKka$$ -f$KCONF"
|
||||
UA="$TEST_TMUX -LtestKua$$ -f$LCONF"
|
||||
$M new-session -d -x80 -y24 -s kitty || exit 1
|
||||
$M new-session -d -x80 -y24 -s csiu || exit 1
|
||||
$KA new-session -d -x80 -y24 "$M attach-session -t kitty" || exit 1
|
||||
$UA new-session -d -x80 -y24 "$M attach-session -t csiu" || exit 1
|
||||
wait_for_mode "$KA" 'Kitty 1' || exit 1
|
||||
|
||||
# Each client must report the protocol its own terminal negotiated.
|
||||
wait_for_client_mode "$M" kitty 'Kitty 1' || exit 1
|
||||
wait_for_client_mode "$M" csiu 'Ext' || exit 1
|
||||
|
||||
uc=$($M list-clients -F '#{client_name} #{client_session}' |
|
||||
awk '$2 == "csiu" { print $1 }')
|
||||
[ -n "$uc" ] || exit 1
|
||||
$M command-prompt -t"$uc" -k 'display-message -pl "%%"' >"$TMP" &
|
||||
pid=$!
|
||||
sleep 0.2
|
||||
$UA send-keys Escape '[97;5u'
|
||||
wait "$pid"
|
||||
[ "$(tr -d '[:space:]' <"$TMP")" = 'C-a' ] || exit 1
|
||||
|
||||
# A Kitty-capable terminal must still be reported when it is not in use.
|
||||
$M set-option -g extended-keys off
|
||||
wait_for_client_mode "$M" kitty 'VT10x (Kitty)' || exit 1
|
||||
wait_for_client_mode "$M" csiu 'VT10x' || exit 1
|
||||
$M set-option -g extended-keys on
|
||||
wait_for_client_mode "$M" kitty 'Kitty 1' || exit 1
|
||||
|
||||
$KA kill-server 2>/dev/null
|
||||
$UA kill-server 2>/dev/null
|
||||
$M kill-server 2>/dev/null
|
||||
|
||||
# Standard extended input must be translated for a Kitty-requesting pane.
|
||||
: >"$OUT"
|
||||
R="$TEST_TMUX -LtestKrt$$ -f$KCONF"
|
||||
RU="$TEST_TMUX -LtestKru$$ -f$LCONF"
|
||||
$R new-session -d -x80 -y24 \
|
||||
"stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5" || exit 1
|
||||
$RU new-session -d -x80 -y24 "$R attach-session" || exit 1
|
||||
wait_for_client_mode "$R" '' 'Ext' || exit 1
|
||||
$RU send-keys C-a
|
||||
wait_for_output "$OUT" || exit 1
|
||||
[ "$(tr -d ' \n' <"$OUT")" = '1b5b39373b3575' ] || exit 1
|
||||
$RU kill-server 2>/dev/null
|
||||
$R kill-server 2>/dev/null
|
||||
|
||||
# An application is told the Kitty flags it asked for even when a client that
|
||||
# only supports VT10x is attached, and keys from that client are encoded as it
|
||||
# asked.
|
||||
: >"$OUT"
|
||||
: >"$TMP"
|
||||
printf '%s\n' "set -as terminal-overrides ',*:Eneks@:Dseks@'" >>"$KCONF"
|
||||
LQ="$TEST_TMUX -LtestKlq$$ -f$KCONF"
|
||||
LU="$TEST_TMUX -LtestKlu$$ -f$LCONF"
|
||||
$LQ new-session -d -x80 -y24 \
|
||||
"stty raw -echo; sleep 2; printf '\033[>1u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$TMP'; sleep 5" || exit 1
|
||||
$LU new-session -d -x80 -y24 "$LQ attach-session" || exit 1
|
||||
wait_for_output "$OUT" || exit 1
|
||||
[ "$(tr -d ' \n' <"$OUT")" = '1b5b3f3175' ] || exit 1
|
||||
wait_for_client_mode "$LQ" '' 'VT10x' || exit 1
|
||||
$LU send-keys C-a
|
||||
wait_for_output "$TMP" || exit 1
|
||||
[ "$(tr -d ' \n' <"$TMP")" = '1b5b39373b3575' ] || exit 1
|
||||
|
||||
exit 0
|
||||
116
regress/kitty-keys-input.sh
Normal file
116
regress/kitty-keys-input.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test client-side Kitty keyboard protocol support: an outer tmux acts as a
|
||||
# Kitty-capable terminal for the inner tmux which is the one under test. The
|
||||
# inner tmux queries the outer one, the outer one replies, and the inner one
|
||||
# then parses the key sequences sent to it.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
CONF=$(mktemp)
|
||||
TMP=$(mktemp)
|
||||
|
||||
printf '%s\n' 'set -g extended-keys on' >"$CONF"
|
||||
|
||||
TMUX="$TEST_TMUX -LtestKIA$$ -f$CONF"
|
||||
TMUX2="$TEST_TMUX -LtestKIB$$ -f$CONF"
|
||||
|
||||
trap 'rm -f "$CONF" "$TMP"; $TMUX kill-server 2>/dev/null; \
|
||||
$TMUX2 kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
$TMUX2 new-session -d -x80 -y24 || exit 1
|
||||
$TMUX new-session -d -x80 -y24 "$TMUX2 attach" || exit 1
|
||||
|
||||
exit_status=0
|
||||
|
||||
wait_for_mode()
|
||||
{
|
||||
i=0
|
||||
while [ "$($TMUX display-message -pt: '#{pane_key_mode}')" != "$1" ] &&
|
||||
[ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$($TMUX display-message -pt: '#{pane_key_mode}')" = "$1" ]
|
||||
}
|
||||
|
||||
assert_key()
|
||||
{
|
||||
keys=$1
|
||||
expected=$2
|
||||
|
||||
$TMUX2 command-prompt -k 'display-message -pl "%%"' >"$TMP" &
|
||||
sleep 0.15
|
||||
# Send raw bytes so the outer tmux does not encode them itself.
|
||||
raw=$(printf '%s' "$keys" | sed -e 's/Escape /\\033/g' -e 's/ //g')
|
||||
$TMUX send-keys -H $(printf "$raw" | od -An -v -t x1)
|
||||
wait
|
||||
|
||||
actual=$(tr -d '[:space:]' <"$TMP")
|
||||
if [ "$actual" = "$expected" ]; then
|
||||
[ -n "$VERBOSE" ] && echo "[PASS] $keys -> $actual"
|
||||
else
|
||||
echo "[FAIL] $keys -> $expected (got '$actual')"
|
||||
exit_status=1
|
||||
fi
|
||||
}
|
||||
|
||||
# The inner tmux must detect the outer one and push its flags.
|
||||
if ! wait_for_mode 'Kitty 1'; then
|
||||
echo "[FAIL] inner tmux did not enable the Kitty protocol"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Modifiers, including the Kitty-only Super and Hyper.
|
||||
assert_key 'Escape [97;2u' 'S-a'
|
||||
assert_key 'Escape [97;3u' 'M-a'
|
||||
assert_key 'Escape [97;5u' 'C-a'
|
||||
assert_key 'Escape [97;6u' 'C-S-a'
|
||||
assert_key 'Escape [97;7u' 'C-M-a'
|
||||
assert_key 'Escape [97;9u' 's-a'
|
||||
assert_key 'Escape [97;17u' 'H-a'
|
||||
assert_key 'Escape [65;2u' 'S-A'
|
||||
|
||||
# Keys with a dedicated tmux key code.
|
||||
assert_key 'Escape [9;5u' 'C-Tab'
|
||||
assert_key 'Escape [9;2u' 'BTab'
|
||||
assert_key 'Escape [13;5u' 'C-Enter'
|
||||
assert_key 'Escape [27u' 'Escape'
|
||||
assert_key 'Escape [32;5u' 'C-Space'
|
||||
assert_key 'Escape [127;5u' 'C-BSpace'
|
||||
|
||||
# Keys with a CSI final byte other than u.
|
||||
assert_key 'Escape [1;5A' 'C-Up'
|
||||
assert_key 'Escape [1;3D' 'M-Left'
|
||||
assert_key 'Escape [1;5H' 'C-Home'
|
||||
assert_key 'Escape [1;5P' 'C-F1'
|
||||
|
||||
# Functional and non-ASCII keys.
|
||||
assert_key 'Escape [57399;5u' 'C-KP0'
|
||||
assert_key 'Escape [233;5u' 'C-é'
|
||||
|
||||
# The alternate key and associated text fields are not requested but must be
|
||||
# ignored if a terminal sends them anyway.
|
||||
assert_key 'Escape [97:65;2u' 'S-a'
|
||||
assert_key 'Escape [97:65:97;5u' 'C-a'
|
||||
assert_key 'Escape [97;5;97u' 'C-a'
|
||||
|
||||
# A repeat event is a key press, a release event is dropped. The C-b that
|
||||
# follows shows that the sequence was consumed and not passed through.
|
||||
assert_key 'Escape [97;5:1u' 'C-a'
|
||||
assert_key 'Escape [97;5:2u' 'C-a'
|
||||
assert_key 'Escape [97;5:3u Escape [98;5u' 'C-b'
|
||||
|
||||
# The Meta modifier bit is not representable and is dropped.
|
||||
assert_key 'Escape [97;33u Escape [98;5u' 'C-b'
|
||||
|
||||
# Lock modifiers are ignored rather than dropping the key.
|
||||
assert_key 'Escape [97;65u' 'a'
|
||||
assert_key 'Escape [97;129u' 'a'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
exit $exit_status
|
||||
85
regress/kitty-keys-terminal.sh
Normal file
85
regress/kitty-keys-terminal.sh
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test the Kitty keyboard protocol against a real terminal rather than against
|
||||
# another copy of tmux. This drives kitty through its remote control interface
|
||||
# so that kitty itself encodes the key presses.
|
||||
#
|
||||
# This has to be run by hand from this directory. The Makefile runs tests with
|
||||
# env -i, which removes DISPLAY and HOME, so kitty cannot start and this exits
|
||||
# immediately without testing anything. It is also skipped unless kitty is
|
||||
# installed and is new enough to have send-key.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
command -v kitty >/dev/null 2>&1 || exit 0
|
||||
[ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ] || exit 0
|
||||
kitty @ --help 2>/dev/null | grep -q 'send-key' || exit 0
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
CONF=$DIR/conf
|
||||
SOCKET=$DIR/kitty
|
||||
OUT=$DIR/out
|
||||
TMUX="$TEST_TMUX -LtestKrt$$ -f$CONF"
|
||||
|
||||
printf '%s\n' 'set -g extended-keys on' \
|
||||
'set -g extended-keys-format kitty' >"$CONF"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
kitty -o allow_remote_control=yes --listen-on "unix:$SOCKET" \
|
||||
-o confirm_os_window_close=0 -e \
|
||||
$TMUX new-session -s r "sleep 120" >"$DIR/err" 2>&1 &
|
||||
|
||||
# The terminfo entry for kitty may be missing, so treat a server which never
|
||||
# appears as a reason to skip rather than as a failure.
|
||||
i=0
|
||||
while ! $TMUX has-session -t r 2>/dev/null && [ "$i" -lt 150 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
$TMUX has-session -t r 2>/dev/null || exit 0
|
||||
|
||||
# The client must negotiate the protocol with the real terminal.
|
||||
i=0
|
||||
while [ "$($TMUX list-clients -F '#{client_key_mode}')" != 'Kitty 1' ] &&
|
||||
[ "$i" -lt 100 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$($TMUX list-clients -F '#{client_key_mode}')" = 'Kitty 1' ] || exit 1
|
||||
|
||||
# kitty encodes the key, tmux parses it and encodes it again for the pane.
|
||||
try_key()
|
||||
{
|
||||
key=$1
|
||||
expected=$2
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t r: \
|
||||
"stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 20" || return 1
|
||||
sleep 1
|
||||
kitty @ --to "unix:$SOCKET" send-key "$key" 2>/dev/null || return 1
|
||||
i=0
|
||||
while [ ! -s "$OUT" ] && [ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(tr -d ' \n' <"$OUT")" = "$expected" ]
|
||||
}
|
||||
|
||||
try_key ctrl+a '1b5b39373b3575' || exit 1
|
||||
try_key alt+a '1b5b39373b3375' || exit 1
|
||||
try_key ctrl+shift+a '1b5b39373b3675' || exit 1
|
||||
|
||||
# Super cannot be represented by the standard extended keys at all, so this
|
||||
# only succeeds if the whole chain used the Kitty protocol.
|
||||
try_key super+a '1b5b39373b3975' || exit 1
|
||||
|
||||
exit 0
|
||||
108
regress/kitty-keys.sh
Normal file
108
regress/kitty-keys.sh
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test pane-side Kitty keyboard protocol state and key encoding. This uses
|
||||
# synthetic protocol sequences and does not require a particular terminal.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
CONF=$(mktemp)
|
||||
OUT=$(mktemp)
|
||||
TMUX="$TEST_TMUX -LtestK$$ -f$CONF"
|
||||
|
||||
trap 'rm -f "$CONF" "$OUT"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
printf '%s\n' 'set -g extended-keys on' >"$CONF"
|
||||
|
||||
wait_for_output()
|
||||
{
|
||||
i=0
|
||||
while [ ! -s "$OUT" ] && [ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -s "$OUT" ]
|
||||
}
|
||||
|
||||
wait_for_mode()
|
||||
{
|
||||
wanted=$1
|
||||
i=0
|
||||
while [ "$($TMUX display-message -pt: '#{pane_key_mode}')" != "$wanted" ] &&
|
||||
[ "$i" -lt 50 ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$($TMUX display-message -pt: '#{pane_key_mode}')" = "$wanted" ]
|
||||
}
|
||||
|
||||
check_output()
|
||||
{
|
||||
expected=$1
|
||||
actual=$(tr -d ' \n' <"$OUT")
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "expected $expected, got $actual"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -x80 -y24 \
|
||||
"stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5" || exit 1
|
||||
wait_for_mode 'Kitty 1'
|
||||
$TMUX send-keys -t: C-S-a
|
||||
wait_for_output
|
||||
check_output 1b5b39373b3675
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_mode 'Kitty 1'
|
||||
$TMUX send-keys -t: s-a
|
||||
wait_for_output
|
||||
check_output 1b5b39373b3975
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>8u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_mode 'Kitty 8'
|
||||
$TMUX send-keys -t: a
|
||||
wait_for_output
|
||||
check_output 1b5b393775
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>1u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_output
|
||||
check_output 1b5b3f3175
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>4;2m\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_output
|
||||
check_output 1b5b3f3075
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>1u\033[>8u\033[<u\033[?u\033[>8u\033[<2u\033[?u'; dd bs=1 count=10 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_output
|
||||
check_output 1b5b3f31751b5b3f3075
|
||||
|
||||
: >"$OUT"
|
||||
# "always" forces modifyOtherKeys mode 1, not Kitty keys.
|
||||
$TMUX set-option -g extended-keys always
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>0u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_output
|
||||
check_output 1b5b3f3075
|
||||
wait_for_mode 'Ext 1'
|
||||
$TMUX set-option -g extended-keys on
|
||||
|
||||
: >"$OUT"
|
||||
$TMUX respawn-pane -k -t: \
|
||||
"stty raw -echo; printf '\033[>1u\033[?u\033[?1049h\033[?u\033[>8u\033[?u\033[?1049l\033[?u'; dd bs=1 count=20 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
|
||||
wait_for_output
|
||||
check_output 1b5b3f31751b5b3f30751b5b3f38751b5b3f3175
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
|
||||
$TMUX set -g @modal-root no
|
||||
$TMUX bind -n z set -g @modal-root yes
|
||||
|
||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -OK failed"
|
||||
fail "new-pane -ODK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
@@ -356,9 +356,50 @@ new_left=$(fmt "$modal" '#{pane_left}')
|
||||
new_top=$(fmt "$modal" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "key-capturing modal pane did not move"
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
|
||||
fail "new-pane -ODK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-c
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
# A dead modal does not close on Escape or C-c without -D.
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit on
|
||||
sleep 2
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
|
||||
# failed-key closes successful panes and retains failed panes until a key.
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||
sleep 2
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||
sleep 2
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||
# the zoomed tiled pane must not unzoom the window.
|
||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||
|
||||
@@ -86,6 +86,11 @@ check_value "-gv status-keys" "vi"
|
||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||
check_value "-gv status-keys" "vi"
|
||||
|
||||
# pane-border-lines accepts rounded as a pane border style.
|
||||
check_ok set -gw pane-border-lines rounded
|
||||
check_value "-gwv pane-border-lines" "rounded"
|
||||
check_ok set -gw pane-border-lines single
|
||||
|
||||
# --- flag options ---------------------------------------------------------
|
||||
#
|
||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||
|
||||
@@ -104,6 +104,25 @@ assert_alive()
|
||||
fi
|
||||
}
|
||||
|
||||
# Compare layout geometry and pane ordering, ignoring the active pane and
|
||||
# last-pane history in the JSON layout. Selection is checked separately.
|
||||
pane_layout()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_layout}' |
|
||||
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
|
||||
}
|
||||
|
||||
check_layout()
|
||||
{
|
||||
out=$(pane_layout "$1")
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Layout for '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# split-window geometry.
|
||||
|
||||
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
|
||||
|
||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||
layout=$(pane_layout P:0)
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
@@ -322,7 +341,7 @@ check_ok select-pane -Z -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Directional selection temporarily restores the full layout to find its
|
||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||
@@ -340,7 +359,7 @@ check_ok select-pane -D -Z -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||
check_ok select-pane -t "$p0"
|
||||
@@ -357,7 +376,7 @@ check_ok select-pane -l -Z -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from a pane prompt must recompose the prompt over the
|
||||
# pane contents restored by damage redraw.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-prompt-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER command-prompt -b -P -t "$CLIENT" -p 'PROMPT-MARK>' \
|
||||
'display-message -- %1' || exit 1
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 10 -w 16 -h 3 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
exit 0
|
||||
@@ -1,155 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving and resizing a popup must restore its old rectangle, including pane
|
||||
# border status, on the attached client's terminal.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-drag-outer-$$ -f/dev/null"
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
RAW=$DIR/raw
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_match()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer rows $first-$last were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
# A left-button border drag moves the popup. The first motion starts the drag;
|
||||
# the second changes its position.
|
||||
move_popup()
|
||||
{
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 26 8 M
|
||||
mouse 0 26 8 m
|
||||
}
|
||||
|
||||
# A right-button bottom-right-border drag resizes the popup.
|
||||
resize_popup()
|
||||
{
|
||||
mouse 2 "$1" "$2" M
|
||||
mouse 34 "$3" "$4" M
|
||||
mouse 34 "$5" "$6" M
|
||||
mouse 2 "$5" "$6" m
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; printf \"\\033[19;45H\\033[38;5;201mSCOPE\\033[0m\"; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -w pane-border-status top || exit 1
|
||||
$INNER set-option -w pane-border-format 'DAMAGE-STATUS-RESTORED' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has SCOPE
|
||||
grep -qa '38;5;201' "$RAW" || fail "scope colour was not drawn initially"
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
grep -q DAMAGE-STATUS-RESTORED "$BASE" ||
|
||||
fail "pane border status was not visible before popup"
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 0 -w 28 -h 6 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
|
||||
move_popup
|
||||
wait_rows_match 1 6
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
grep -qa '38;5;201' "$RAW" &&
|
||||
fail "popup move redrew content outside its old and new rectangles"
|
||||
grep -q DAMAGE-STATUS-RESTORED "$CAPTURE" ||
|
||||
fail "pane border status was not restored after popup move"
|
||||
|
||||
# The moved popup is 28x6 at zero-based 16,7. Shrink it to 21x3, then require
|
||||
# the three rows vacated at the bottom to match the original scene.
|
||||
resize_popup 44 13 43 13 38 11
|
||||
wait_rows_match 11 13
|
||||
|
||||
# Grow it back to 28x6 and require a single, intact popup frame.
|
||||
resize_popup 37 10 36 10 45 14
|
||||
wait_outer_has POPUP-MARKER
|
||||
corners=$(grep -o '┌' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 1 ] || fail "expected one popup frame, found $corners"
|
||||
|
||||
exit 0
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from the tmux status area must restore status and window
|
||||
# cells. Window damage alone cannot describe cells outside the window scene,
|
||||
# and top status lines must be removed before translating to window rows.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
POPUP_PID=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup_scene()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
POPUP_PID=
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
cleanup_scene
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_restored()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "rows $first-$last under the old popup were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
position=$1
|
||||
lines=$2
|
||||
|
||||
cleanup_scene
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-status-outer-$$-$N -f/dev/null"
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
if [ "$lines" -eq 1 ]; then
|
||||
$INNER set-option -g status on || exit 1
|
||||
else
|
||||
$INNER set-option -g status "$lines" || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-position "$position" || exit 1
|
||||
$INNER set-option -g status-format[0] \
|
||||
'STATUS-LINE-MARK-01234567890123456789012' || exit 1
|
||||
if [ "$lines" -gt 1 ]; then
|
||||
$INNER set-option -g status-format[1] \
|
||||
'SECOND-STATUS-MARK-012345678901234567890' || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has STATUS-LINE-MARK
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
}
|
||||
|
||||
open_popup()
|
||||
{
|
||||
y=$1
|
||||
height=$2
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y "$y" -w 16 -h "$height" -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
}
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != top ]; then
|
||||
# A popup clipped against a one-line bottom status must restore that line.
|
||||
setup bottom 1
|
||||
open_popup 10 3
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_rows_restored 10 10
|
||||
fi
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != bottom ]; then
|
||||
# A popup beginning in a two-line top status also covers the first window
|
||||
# rows. Restore both coordinate spaces after it moves.
|
||||
setup top 2
|
||||
open_popup 1 4
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 25 7 M
|
||||
mouse 0 25 7 m
|
||||
wait_rows_restored 1 4
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
||||
# wide character's base or padding cell leaves a two-cell hole behind.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_old_rows_restored()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n '3,5p' "$BASE" >"$DIR/want"
|
||||
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "wide characters under the old popup edge were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
$| = 1;
|
||||
for my $row (1 .. 10) {
|
||||
print "\e[$row;1H", chr(0x754c) x 20;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has '界界界'
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
||||
"sh -c 'printf POPUP; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP
|
||||
|
||||
# The first motion starts the drag; the second moves the popup away from its
|
||||
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
||||
# cells at both edges.
|
||||
mouse 0 10 3 M
|
||||
mouse 32 11 3 M
|
||||
mouse 32 28 7 M
|
||||
mouse 0 28 7 m
|
||||
wait_old_rows_restored
|
||||
|
||||
exit 0
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 12) {
|
||||
print "\e[$row;1H", 'o' x 79;
|
||||
}
|
||||
print "\e[1;1H";
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||
print "\e[5;1H", ('A' x 80), $second;
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||
wait_outer_has oooooooooo
|
||||
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has DAMAGE-ONLY
|
||||
wait_outer_has DAMAGE-ONLY
|
||||
|
||||
exit 0
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Redraw a moved floating pane on both attached clients viewing the same
|
||||
# window. Window redraw work must not be consumed by only one client.
|
||||
#
|
||||
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||
# this test nests a real tmux client inside another tmux's pane to get a
|
||||
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||
# character being overwritten later by plain content, on the outer instance's
|
||||
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||
# the exact same drag sequence against a real terminal (xterm), where it
|
||||
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer pane $target did not show $marker"
|
||||
}
|
||||
|
||||
wait_float_left()
|
||||
{
|
||||
comparison=$1
|
||||
limit=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "floating pane did not move"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
drag_float()
|
||||
{
|
||||
target=$1
|
||||
startcol=$2
|
||||
startrow=$3
|
||||
endcol=$4
|
||||
mouse "$target" 0 "$startcol" "$startrow" M
|
||||
mouse "$target" 32 "$endcol" "$startrow" M
|
||||
mouse "$target" 0 "$endcol" "$startrow" m
|
||||
}
|
||||
|
||||
assert_scene()
|
||||
{
|
||||
target=$1
|
||||
base=$2
|
||||
firstcol=$3
|
||||
lastcol=$4
|
||||
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
|
||||
# With ASCII (simple) borders every corner and junction is the same
|
||||
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||
# them; more means a stale frame was left behind somewhere.
|
||||
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 4 ] ||
|
||||
fail "outer pane $target had $corners floating frames"
|
||||
|
||||
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" ||
|
||||
fail "outer pane $target did not restore the old floating area"
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||
$INNER set-option -g pane-border-lines simple || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||
|
||||
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||
|
||||
$OUTER respawn-pane -k -t "$LEFT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_clients
|
||||
wait_outer_has "$LEFT" BG-ROW-19
|
||||
wait_outer_has "$RIGHT" BG-ROW-19
|
||||
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||
|
||||
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "could not create floating pane"
|
||||
wait_outer_has "$LEFT" '+'
|
||||
wait_outer_has "$RIGHT" '+'
|
||||
|
||||
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
|
||||
# Move right through one client and require both clients to restore the old
|
||||
# left-hand footprint.
|
||||
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||
wait_float_left gt 30
|
||||
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||
|
||||
# Move back through the other client and check the old right-hand footprint.
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||
wait_float_left lt 10
|
||||
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||
|
||||
exit 0
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that full and region screen-write fallbacks update an attached client,
|
||||
# not only tmux's internal pane grid.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_outer_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client still showed $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
wait_inner_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane still contained $marker"
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
mode=$1
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||
exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
}
|
||||
|
||||
trigger()
|
||||
{
|
||||
: >"$DIR/trigger-$N-${1:-1}"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
my $mode = $ENV{MODE};
|
||||
my $ready = $ENV{READY};
|
||||
my $trigger = $ENV{TRIGGER};
|
||||
|
||||
sub fill_screen {
|
||||
my ($prefix) = @_;
|
||||
print "\e[2J\e[H";
|
||||
for my $row (0 .. 11) {
|
||||
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
fill_screen('RIS');
|
||||
} elsif ($mode eq 'alternate') {
|
||||
fill_screen('BASE');
|
||||
} elsif ($mode eq 'scroll') {
|
||||
fill_screen('SCROLL');
|
||||
} else {
|
||||
die "unknown mode $mode\n";
|
||||
}
|
||||
|
||||
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||
close $fh;
|
||||
while (!-e "$trigger-1") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
print "\ec";
|
||||
} elsif ($mode eq 'alternate') {
|
||||
print "\e[?1049h";
|
||||
fill_screen('ALT');
|
||||
while (!-e "$trigger-2") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049l";
|
||||
} else {
|
||||
print "\e[12;1H\r\nSCROLL-NEW";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# RIS clears the complete screen. The source pane and attached client must both
|
||||
# lose every old row.
|
||||
setup ris
|
||||
wait_outer_has RIS-ROW-11
|
||||
trigger
|
||||
wait_inner_lacks RIS-ROW
|
||||
wait_outer_lacks RIS-ROW
|
||||
|
||||
# Leaving the alternate screen restores every row of the base screen.
|
||||
setup alternate
|
||||
wait_outer_has BASE-ROW-11
|
||||
trigger
|
||||
wait_inner_has ALT-ROW-11
|
||||
wait_outer_has ALT-ROW-11
|
||||
wait_outer_lacks BASE-ROW
|
||||
trigger 2
|
||||
wait_inner_has BASE-ROW-11
|
||||
wait_outer_has BASE-ROW-11
|
||||
wait_outer_lacks ALT-ROW
|
||||
|
||||
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||
# region. Check the physical client row by row after the source grid shifts.
|
||||
setup scroll
|
||||
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||
wait_outer_has SCROLL-ROW-11
|
||||
trigger
|
||||
wait_inner_has SCROLL-NEW
|
||||
wait_outer_has SCROLL-NEW
|
||||
row=1
|
||||
while [ "$row" -le 11 ]; do
|
||||
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||
case "$actual" in
|
||||
"$expected"*) ;;
|
||||
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||
esac
|
||||
row=$((row + 1))
|
||||
done
|
||||
actual=$(sed -n '12p' "$CAPTURE")
|
||||
case "$actual" in
|
||||
SCROLL-NEW*) ;;
|
||||
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -107,6 +107,12 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
|
||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
compare floating-border-double
|
||||
|
||||
# Larger floating pane with rounded border lines.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
|
||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
compare floating-border-rounded
|
||||
|
||||
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
||||
# the float has no border at all, only its (clipped) content over the base pane.
|
||||
new_scene 40 12
|
||||
|
||||
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
@@ -0,0 +1,12 @@
|
||||
base
|
||||
╭──────────────────────────╮
|
||||
│FLOAT │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Switching windows in the same session must redraw the attached client. The
|
||||
# session object is shared, so its current winlink cannot be compared after it
|
||||
# has already been changed.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 8 \
|
||||
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER new-window -d -t inner:1 \
|
||||
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has A-WINDOW
|
||||
|
||||
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
||||
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
||||
fail "server did not select window 1"
|
||||
wait_outer_has B-WINDOW
|
||||
|
||||
exit 0
|
||||
389
screen-redraw.c
389
screen-redraw.c
@@ -205,22 +205,6 @@ struct redraw_scene {
|
||||
u_int oy;
|
||||
};
|
||||
|
||||
/* A single damaged window-coordinate rectangle. */
|
||||
struct redraw_damage {
|
||||
u_int x;
|
||||
u_int y;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
|
||||
TAILQ_ENTRY(redraw_damage) entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* Cap on the number of pending damage rectangles per window before they are
|
||||
* collapsed into a single rectangle covering their union.
|
||||
*/
|
||||
#define REDRAW_DAMAGE_MAX 16
|
||||
|
||||
/* Cell for building the scene. */
|
||||
struct redraw_build_cell {
|
||||
struct redraw_span_data data;
|
||||
@@ -1073,13 +1057,6 @@ redraw_free_scene(struct redraw_scene *scene)
|
||||
free(scene);
|
||||
}
|
||||
|
||||
/* Does a client's cached scene show this window? */
|
||||
int
|
||||
redraw_client_has_window(struct client *c, struct window *w)
|
||||
{
|
||||
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||
}
|
||||
|
||||
/* Mark a window's cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_scene(struct window *w)
|
||||
@@ -1087,114 +1064,6 @@ redraw_invalidate_scene(struct window *w)
|
||||
w->redraw_scene_generation++;
|
||||
}
|
||||
|
||||
/* Free all pending damage for a window. */
|
||||
void
|
||||
redraw_free_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
w->damage_count = 0;
|
||||
}
|
||||
|
||||
/* Collapse all pending damage for a window into one rectangle - its union. */
|
||||
static void
|
||||
redraw_collapse_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1, *first;
|
||||
u_int x0, y0, x1, y1;
|
||||
|
||||
first = TAILQ_FIRST(&w->damage);
|
||||
if (first == NULL)
|
||||
return;
|
||||
x0 = first->x;
|
||||
y0 = first->y;
|
||||
x1 = first->x + first->sx;
|
||||
y1 = first->y + first->sy;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
if (rd->x < x0)
|
||||
x0 = rd->x;
|
||||
if (rd->y < y0)
|
||||
y0 = rd->y;
|
||||
if (rd->x + rd->sx > x1)
|
||||
x1 = rd->x + rd->sx;
|
||||
if (rd->y + rd->sy > y1)
|
||||
y1 = rd->y + rd->sy;
|
||||
if (rd != first) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
}
|
||||
|
||||
first->x = x0;
|
||||
first->y = y0;
|
||||
first->sx = x1 - x0;
|
||||
first->sy = y1 - y0;
|
||||
w->damage_count = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a damaged window-coordinate rectangle. Clips it to the window,
|
||||
* merges it with an existing rectangle where doing so does not make the
|
||||
* result substantially larger than the two combined, and collapses the
|
||||
* whole list to its union once it grows past a modest cap.
|
||||
*
|
||||
* This only records damage - nothing consumes it yet.
|
||||
*/
|
||||
void
|
||||
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
|
||||
{
|
||||
struct redraw_damage *rd;
|
||||
u_int x0, y0, x1, y1, area, union_area;
|
||||
|
||||
if (x >= w->sx || y >= w->sy)
|
||||
return;
|
||||
if (x + sx > w->sx)
|
||||
sx = w->sx - x;
|
||||
if (y + sy > w->sy)
|
||||
sy = w->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
/* Skip unless overlapping or directly adjacent. */
|
||||
if (x > rd->x + rd->sx || rd->x > x + sx ||
|
||||
y > rd->y + rd->sy || rd->y > y + sy)
|
||||
continue;
|
||||
|
||||
x0 = (x < rd->x) ? x : rd->x;
|
||||
y0 = (y < rd->y) ? y : rd->y;
|
||||
x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx;
|
||||
y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy;
|
||||
|
||||
area = sx * sy + rd->sx * rd->sy;
|
||||
union_area = (x1 - x0) * (y1 - y0);
|
||||
if (union_area > 2 * area)
|
||||
continue;
|
||||
|
||||
rd->x = x0;
|
||||
rd->y = y0;
|
||||
rd->sx = x1 - x0;
|
||||
rd->sy = y1 - y0;
|
||||
return;
|
||||
}
|
||||
|
||||
rd = xcalloc(1, sizeof *rd);
|
||||
rd->x = x;
|
||||
rd->y = y;
|
||||
rd->sx = sx;
|
||||
rd->sy = sy;
|
||||
TAILQ_INSERT_TAIL(&w->damage, rd, entry);
|
||||
w->damage_count++;
|
||||
|
||||
if (w->damage_count > REDRAW_DAMAGE_MAX)
|
||||
redraw_collapse_damage(w);
|
||||
}
|
||||
|
||||
/* Mark all cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_all_scenes(void)
|
||||
@@ -1507,15 +1376,10 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
||||
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw a span, restricted to [clip_x, clip_x + clip_n) - a caller drawing
|
||||
* the whole span passes the span's own x/width here; a caller drawing only
|
||||
* a damaged sub-range passes that range instead. Overlay clipping (menus,
|
||||
* popups) is then applied on top of this, exactly as before.
|
||||
*/
|
||||
/* Draw a span. */
|
||||
static void
|
||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
u_int y, u_int clip_x, u_int clip_n)
|
||||
u_int y)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_span_data *data = &span->data;
|
||||
@@ -1529,7 +1393,7 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
|
||||
r = tty_check_overlay_range(tty, clip_x, y, clip_n);
|
||||
r = tty_check_overlay_range(tty, span->x, y, span->width);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
@@ -1589,19 +1453,15 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp) {
|
||||
redraw_draw_span(dctx, span, cy,
|
||||
span->x, span->width);
|
||||
}
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp) {
|
||||
redraw_draw_span(dctx, span, cy,
|
||||
span->x, span->width);
|
||||
}
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1659,10 +1519,8 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
}
|
||||
}
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width);
|
||||
}
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1682,10 +1540,8 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) {
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width);
|
||||
}
|
||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1765,24 +1621,6 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
||||
dctx->flags |= REDRAW_ISOLATES;
|
||||
}
|
||||
|
||||
/* Build a pane prompt into a one-line screen. */
|
||||
static void
|
||||
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||
{
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
|
||||
screen_init(screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
}
|
||||
|
||||
/* Draw a pane's prompt over its content. */
|
||||
static void
|
||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
@@ -1791,6 +1629,8 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
struct client *c = scene->c;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen screen;
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
int ox = scene->ox, oy = scene->oy;
|
||||
int sx = scene->sx, sy = scene->sy;
|
||||
int line, cy, px, offset, width, wy;
|
||||
@@ -1823,7 +1663,16 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
if (px + width > sx)
|
||||
width = sx - px;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
screen_init(&screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, &screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
||||
screen_free(&screen);
|
||||
}
|
||||
@@ -1855,8 +1704,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
redraw = status_prompt_redraw(c);
|
||||
else
|
||||
redraw = status_redraw(c);
|
||||
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||
!REDRAW_IS_ALL(flags)) {
|
||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
||||
flags &= ~REDRAW_STATUS;
|
||||
if (flags == 0)
|
||||
return;
|
||||
@@ -2027,7 +1875,7 @@ redraw_screen(struct client *c)
|
||||
} else {
|
||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
||||
flags |= REDRAW_OVERLAY;
|
||||
@@ -2055,190 +1903,3 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
||||
}
|
||||
|
||||
/*
|
||||
* A REDRAW_SPAN_STATUS span within a damaged rectangle needs its content
|
||||
* rebuilt and force-drawn regardless of whether that content has logically
|
||||
* changed. window_make_pane_status()'s grid_compare() only tells us
|
||||
* whether the *content* changed, not whether the physical cells were
|
||||
* disturbed by something else (e.g. a floating pane sliding across this
|
||||
* row) - and being inside a damage rectangle already proves that happened.
|
||||
* Without this, redraw_draw_span() silently skips REDRAW_SPAN_STATUS spans
|
||||
* whenever PANE_NEWSTATUS is not set, leaving a pane's border-status title
|
||||
* blank until some unrelated redraw happens to touch it (e.g. a focus
|
||||
* change or window resize).
|
||||
*/
|
||||
static void
|
||||
redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct redraw_span *first;
|
||||
u_int width;
|
||||
|
||||
if (wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
width = redraw_pane_status_width(dctx, wp, &first);
|
||||
if (width == 0)
|
||||
return;
|
||||
window_make_pane_status(wp, dctx->scene->c, width, first);
|
||||
wp->flags |= PANE_NEWSTATUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||
* the grid) may be sitting on the second, padding half of a wide character
|
||||
* whose first half falls just outside the requested range - growing by one
|
||||
* cell is always enough to pull the whole character back in, since no grid
|
||||
* cell is ever wider than two columns, and clamping to the span's own x and
|
||||
* width keeps this from bleeding into a neighbouring span.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||
{
|
||||
if (*xp > span->x)
|
||||
(*xp)--;
|
||||
if (*endp < span->x + span->width)
|
||||
(*endp)++;
|
||||
}
|
||||
|
||||
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||
static void
|
||||
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct window_pane *wp = span->data.p.wp;
|
||||
struct tty *tty = &scene->c->tty;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
struct screen screen;
|
||||
u_int i, px, width, prompt_y;
|
||||
|
||||
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||
return;
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
prompt_y = 0;
|
||||
else
|
||||
prompt_y = wp->sy - 1;
|
||||
if (span->data.p.py != prompt_y)
|
||||
return;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
r = tty_check_overlay_range(tty, x, y, n);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
px = span->data.p.px + (rr->px - span->x);
|
||||
if (px >= screen_size_x(&screen))
|
||||
continue;
|
||||
width = rr->nx;
|
||||
if (width > screen_size_x(&screen) - px)
|
||||
width = screen_size_x(&screen) - px;
|
||||
tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL);
|
||||
}
|
||||
screen_free(&screen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compose exactly the cells within a damaged rectangle (already in this
|
||||
* client's own scene coordinates), rather than a whole pane. For each row
|
||||
* in range, every span of every type whose x-range intersects the
|
||||
* rectangle is drawn restricted to just the intersected sub-range.
|
||||
*/
|
||||
static void
|
||||
redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
u_int cy, yy, clip_x, clip_end, type;
|
||||
|
||||
if (x >= scene->sx || y >= scene->sy)
|
||||
return;
|
||||
if (x + sx > scene->sx)
|
||||
sx = scene->sx - x;
|
||||
if (y + sy > scene->sy)
|
||||
sy = scene->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + yy;
|
||||
else
|
||||
cy = yy;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
clip_x = (span->x > x) ? span->x : x;
|
||||
clip_end = (span->x + span->width < x + sx) ?
|
||||
span->x + span->width : x + sx;
|
||||
if (clip_end <= clip_x)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS) {
|
||||
redraw_damage_refresh_status(dctx,
|
||||
span->data.st.wp);
|
||||
}
|
||||
redraw_damage_grow_span_clip(span, &clip_x,
|
||||
&clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
if (type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(dctx,
|
||||
span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Consume a client's window's pending damage by composing exactly the
|
||||
* damaged cells, after clipping each rectangle to what this client can see
|
||||
* and translating it into this client's own scene coordinates.
|
||||
*
|
||||
* Unlike redraw_pane(), this does not redraw a whole pane's worth of cells
|
||||
* for a small disturbance - only the cells within the (clipped) rectangle
|
||||
* are touched, via redraw_draw_damage_rect().
|
||||
*/
|
||||
void
|
||||
redraw_client_damage(struct client *c)
|
||||
{
|
||||
struct window *w = c->session->curw->window;
|
||||
struct redraw_scene *scene;
|
||||
struct redraw_draw_ctx dctx;
|
||||
struct redraw_damage *rd;
|
||||
u_int ox, oy, sx, sy, x0, y0, x1, y1;
|
||||
|
||||
if (TAILQ_EMPTY(&w->damage))
|
||||
return;
|
||||
|
||||
scene = redraw_get_scene(c);
|
||||
if (scene == NULL)
|
||||
return;
|
||||
redraw_set_draw_context(&dctx, scene);
|
||||
redraw_get_window_offset(c, &ox, &oy, &sx, &sy);
|
||||
|
||||
tty_sync_start(&c->tty);
|
||||
tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
x0 = (rd->x > ox) ? rd->x : ox;
|
||||
y0 = (rd->y > oy) ? rd->y : oy;
|
||||
x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx;
|
||||
y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy;
|
||||
if (x0 >= x1 || y0 >= y1)
|
||||
continue;
|
||||
log_debug("%s: %s composing damage %u,%u %ux%u", __func__,
|
||||
c->name, x0 - ox, y0 - oy, x1 - x0, y1 - y0);
|
||||
redraw_draw_damage_rect(&dctx, x0 - ox, y0 - oy, x1 - x0,
|
||||
y1 - y0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,20 +120,14 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
|
||||
evtimer_add(&w->offset_timer, &tv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a write could not be applied directly to the terminal and
|
||||
* needs a redraw instead. Report damage for the requested rows. wp->yoff is
|
||||
* already adjusted past any top pane-border-status row, so wp->yoff + py is
|
||||
* the correct window-coordinate row.
|
||||
*/
|
||||
/* Do a full redraw. */
|
||||
static void
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (wp == NULL)
|
||||
return;
|
||||
redraw_damage_window(wp->window, wp->xoff, wp->yoff + py, wp->sx, ny);
|
||||
if (wp != NULL)
|
||||
wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
/* Update context for client. */
|
||||
@@ -445,6 +439,7 @@ screen_write_reset(struct screen_write_ctx *ctx)
|
||||
|
||||
if (options_get_number(global_options, "extended-keys") == 2)
|
||||
s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED;
|
||||
input_kitty_reset(s);
|
||||
|
||||
screen_write_clearscreen(ctx, 8);
|
||||
screen_write_set_cursor(ctx, 0, 0);
|
||||
@@ -2161,7 +2156,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
/* Trim collected items. */
|
||||
@@ -3181,7 +3176,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
/* Turn alternate screen off. */
|
||||
@@ -3206,5 +3201,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
6
screen.c
6
screen.c
@@ -88,6 +88,8 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->default_cstyle = SCREEN_CURSOR_DEFAULT;
|
||||
s->mode = MODE_CURSOR;
|
||||
s->default_mode = 0;
|
||||
memset(&s->kitty_keys, 0, sizeof s->kitty_keys);
|
||||
memset(&s->saved_kitty_keys, 0, sizeof s->saved_kitty_keys);
|
||||
s->ccolour = -1;
|
||||
s->default_ccolour = -1;
|
||||
s->tabs = NULL;
|
||||
@@ -118,9 +120,9 @@ screen_reinit(struct screen *s, int check)
|
||||
|
||||
if (options_get_number(global_options, "extended-keys") == 2)
|
||||
s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED;
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
screen_alternate_off(s, NULL, 0);
|
||||
input_kitty_reset(s);
|
||||
s->saved_cx = UINT_MAX;
|
||||
s->saved_cy = UINT_MAX;
|
||||
|
||||
@@ -698,6 +700,7 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
return 0;
|
||||
input_kitty_alternate_on(s);
|
||||
sx = screen_size_x(s);
|
||||
sy = screen_size_y(s);
|
||||
|
||||
@@ -758,6 +761,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
s->cy = screen_size_y(s) - 1;
|
||||
return 0;
|
||||
}
|
||||
input_kitty_alternate_off(s);
|
||||
|
||||
/* Restore the saved grid. */
|
||||
grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0,
|
||||
|
||||
103
server-client.c
103
server-client.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-client.c,v 1.511 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: server-client.c,v 1.512 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -473,33 +473,7 @@ server_client_set_session(struct client *c, struct session *s)
|
||||
tty_update_client_offset(c);
|
||||
status_timer_start(c);
|
||||
server_client_fire_session_changed(c, old);
|
||||
|
||||
/*
|
||||
* A full redraw is only needed if the client's session or
|
||||
* current window actually changed - not if this merely
|
||||
* confirmed the client is still looking at the same window
|
||||
* (as happens when switch-client -t targets a pane in the
|
||||
* already-current window, e.g. clicking a pane name in a
|
||||
* second #{P:} status line: the default MouseDown1Status
|
||||
* binding resolves that click to switch-client -t=, which
|
||||
* reaches here regardless of whether anything besides the
|
||||
* active pane changed). Redrawing unconditionally here
|
||||
* forced a full window redraw for what should have been
|
||||
* just an active-pane change, already handled narrowly by
|
||||
* window_set_active_pane() and window_redraw_active_switch()
|
||||
* before this is reached.
|
||||
*
|
||||
* old and s may be the same session object, whose curw was
|
||||
* already updated to the new window before this function was
|
||||
* called - old->curw and s->curw would then read the same,
|
||||
* already-current value, so comparing them can never detect
|
||||
* a same-session window change. Compare against the client's
|
||||
* own cached scene instead, which only reflects what it has
|
||||
* actually drawn.
|
||||
*/
|
||||
if (old == NULL || old != s ||
|
||||
!redraw_client_has_window(c, s->curw->window))
|
||||
server_redraw_client(c);
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
server_check_unattached();
|
||||
@@ -1417,15 +1391,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
||||
return (repeat);
|
||||
}
|
||||
|
||||
/* Handle a key press on a dead pane waiting for a key. */
|
||||
/* Handle a key press which closes a dead pane. */
|
||||
static int
|
||||
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||
{
|
||||
int remain_on_exit;
|
||||
|
||||
if (wp == NULL ||
|
||||
(~wp->flags & PANE_EXITED) ||
|
||||
KEYC_IS_MOUSE(key) ||
|
||||
KEYC_IS_PASTE(key) ||
|
||||
options_get_number(wp->options, "remain-on-exit") != 3)
|
||||
KEYC_IS_PASTE(key))
|
||||
return (0);
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
if (remain_on_exit != 3 && remain_on_exit != 4)
|
||||
return (0);
|
||||
options_set_number(wp->options, "remain-on-exit", 0);
|
||||
server_destroy_pane(wp, 0);
|
||||
@@ -1488,19 +1466,8 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
/*
|
||||
* Mouse drag is in progress, so fire the callback (now that
|
||||
* the mouse event is valid).
|
||||
*
|
||||
* Start a synchronized-output region here rather than
|
||||
* leaving it to whatever redraw eventually follows: a drag
|
||||
* callback may write directly via the pane's fast path
|
||||
* immediately, with any correction only arriving later via
|
||||
* redraw_client_damage(), which opens its own sync region.
|
||||
* Since tty_sync_end() is only called once, at the very end
|
||||
* of this client's pass in server_client_reset_state(),
|
||||
* starting it here merges both into one atomic terminal
|
||||
* update instead of two visible frames.
|
||||
*/
|
||||
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
||||
tty_sync_start(&c->tty);
|
||||
c->tty.mouse_drag_update(c, m);
|
||||
goto out;
|
||||
}
|
||||
@@ -1793,9 +1760,10 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
}
|
||||
|
||||
/*
|
||||
* Key presses in overlay mode, for panes capturing all keys and in the
|
||||
* command prompt are a special case. The queue might be blocked so they
|
||||
* need to be processed immediately rather than queued.
|
||||
* Key presses in overlay mode, dead panes waiting for a key, modal cancel
|
||||
* keys, panes capturing all keys and the command prompt are special cases.
|
||||
* The queue might be blocked so they need to be processed immediately
|
||||
* rather than queued.
|
||||
*/
|
||||
if (~c->flags & CLIENT_READONLY) {
|
||||
if (c->message_string != NULL) {
|
||||
@@ -1817,12 +1785,18 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
server_client_clear_overlay(c);
|
||||
|
||||
wp = s->curw->window->active;
|
||||
if (server_client_handle_dead_key(wp, event->key))
|
||||
return (0);
|
||||
if (wp != NULL && wp == wp->window->modal &&
|
||||
(wp->flags & PANE_CLOSEONCANCEL) &&
|
||||
(event->key == '\033' || event->key == ('c'|KEYC_CTRL))) {
|
||||
server_kill_pane(wp);
|
||||
return (0);
|
||||
}
|
||||
if (wp != NULL &&
|
||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||
TAILQ_EMPTY(&wp->modes) &&
|
||||
!KEYC_IS_MOUSE(event->key)) {
|
||||
if (server_client_handle_dead_key(wp, event->key))
|
||||
return (0);
|
||||
if (~wp->flags & PANE_EXITED) {
|
||||
window_pane_key(wp, c, s, s->curw, event->key,
|
||||
&event->m);
|
||||
@@ -1937,12 +1911,7 @@ server_client_loop(void)
|
||||
|
||||
/*
|
||||
* Any windows will have been redrawn as part of clients, so clear
|
||||
* their flags now. A client whose redraw was deferred this pass
|
||||
* (waiting for outstanding tty output to drain) has already
|
||||
* escalated to CLIENT_REDRAWWINDOW or CLIENT_REDRAWSCROLLBARS in
|
||||
* server_client_check_redraw() to cover whatever it is about to
|
||||
* lose here, so PANE_REDRAW/PANE_REDRAWSCROLLBAR and window damage
|
||||
* can simply be cleared unconditionally.
|
||||
* their flags now.
|
||||
*/
|
||||
RB_FOREACH(w, windows, &windows) {
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
@@ -1953,8 +1922,6 @@ server_client_loop(void)
|
||||
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
||||
PANE_ACTIVITY);
|
||||
}
|
||||
redraw_free_damage(w);
|
||||
|
||||
check_window_name(w);
|
||||
}
|
||||
|
||||
@@ -2227,6 +2194,7 @@ server_client_reset_state(struct client *c)
|
||||
log_debug("%s: client %s mode %s", __func__, c->name,
|
||||
screen_mode_to_string(mode));
|
||||
}
|
||||
tty_update_kitty(tty, s);
|
||||
|
||||
/* Reset region and margin. */
|
||||
tty_region_off(tty);
|
||||
@@ -2503,8 +2471,6 @@ server_client_any_pane_redraw(struct client *c)
|
||||
|
||||
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||
return (1);
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
return (1);
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
||||
return (1);
|
||||
@@ -2550,12 +2516,7 @@ server_client_check_redraw(struct client *c)
|
||||
/*
|
||||
* If there is outstanding data, defer the redraw until it has been
|
||||
* consumed. We can just add a timer to get out of the event loop and
|
||||
* end up back here. server_client_loop() clears PANE_REDRAW,
|
||||
* PANE_REDRAWSCROLLBAR and window damage unconditionally every pass,
|
||||
* so escalate to a coarser, persistent client flag that survives
|
||||
* that clear and forces a full catch-up redraw once this client is
|
||||
* unblocked, rather than trying to keep the fine-grained state
|
||||
* around for a retry.
|
||||
* end up back here.
|
||||
*/
|
||||
n = EVBUFFER_LENGTH(tty->out);
|
||||
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
||||
@@ -2569,8 +2530,6 @@ server_client_check_redraw(struct client *c)
|
||||
log_debug("redraw timer started");
|
||||
evtimer_add(&ev, &tv);
|
||||
}
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & PANE_REDRAW) {
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
@@ -2604,19 +2563,6 @@ server_client_check_redraw(struct client *c)
|
||||
redraw_pane_scrollbar(c, wp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Window damage is also what makes server_client_any_pane_
|
||||
* redraw() decide a redraw is needed at all, independently of
|
||||
* any CLIENT_ALLREDRAWFLAGS bit. Every current damage source
|
||||
* happens to set one of those flags too, so the block below
|
||||
* always consumes it - but consume it here too in case that
|
||||
* ever stops holding, since server_client_loop() clears
|
||||
* window damage unconditionally every pass regardless of
|
||||
* whether it was actually drawn.
|
||||
*/
|
||||
if (!TAILQ_EMPTY(&w->damage) && (~c->flags & CLIENT_ALLREDRAWFLAGS))
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2630,7 +2576,6 @@ server_client_check_redraw(struct client *c)
|
||||
}
|
||||
server_client_set_progress_bar(c);
|
||||
redraw_screen(c);
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/* Put the tty back how it was. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
||||
/* $OpenBSD: server-fn.c,v 1.153 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
||||
case 0:
|
||||
break;
|
||||
case 2:
|
||||
case 4:
|
||||
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
||||
break;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
75
tmux.1
75
tmux.1
@@ -1,4 +1,4 @@
|
||||
.\" $OpenBSD: tmux.1,v 1.1168 2026/09/09 07:03:39 nicm Exp $
|
||||
.\" $OpenBSD: tmux.1,v 1.1170 2026/09/11 10:17:16 nicm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
.\"
|
||||
@@ -14,7 +14,7 @@
|
||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.\"
|
||||
.Dd $Mdocdate: September 9 2026 $
|
||||
.Dd $Mdocdate: September 11 2026 $
|
||||
.Dt TMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -3655,7 +3655,7 @@ but a different format may be specified with
|
||||
.Fl F .
|
||||
.Tg newp
|
||||
.It Xo Ic new\-pane
|
||||
.Op Fl AbCdefhIkKLMOPvWZ
|
||||
.Op Fl AbCDefhIkKLMOPvWZ
|
||||
.Op Fl B Ar border\-lines
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
@@ -3717,6 +3717,13 @@ all keys, including the prefix key, are passed directly to the modal pane.
|
||||
With
|
||||
.Fl C ,
|
||||
the modal pane is closed when the mouse is clicked outside it.
|
||||
With
|
||||
.Fl D ,
|
||||
the modal pane is closed when
|
||||
.Ql Escape
|
||||
or
|
||||
.Ql C-c
|
||||
is pressed.
|
||||
.Pp
|
||||
The
|
||||
.Fl L
|
||||
@@ -4900,14 +4907,30 @@ If enabled, the server will exit when there are no attached clients.
|
||||
.It Xo Ic extended\-keys
|
||||
.Op Ic on | off | always
|
||||
.Xc
|
||||
Controls how modified keys (keys pressed together with Control, Meta, or Shift)
|
||||
are reported.
|
||||
This is the equivalent of the
|
||||
Controls how modified keys are reported.
|
||||
When enabled,
|
||||
.Nm
|
||||
uses the Kitty keyboard protocol with terminals which support it, and the
|
||||
.Ic modifyOtherKeys
|
||||
.Xr xterm 1
|
||||
resource.
|
||||
resource with other terminals which support extended keys.
|
||||
Each client uses the best that its terminal supports, so clients with different
|
||||
terminals may be attached at the same time.
|
||||
.Pp
|
||||
When set to
|
||||
Programs inside panes may request either the Kitty keyboard protocol or
|
||||
.Ic modifyOtherKeys ,
|
||||
and keys are sent to each program in the form it requested, whichever client
|
||||
they were typed on.
|
||||
The Kitty keyboard protocol can report the Super and Hyper modifiers, whose key
|
||||
name prefixes are
|
||||
.Ql s-
|
||||
and
|
||||
.Ql H- ;
|
||||
its disambiguate and report-all enhancements are supported.
|
||||
.Pp
|
||||
For
|
||||
.Ic modifyOtherKeys ,
|
||||
when set to
|
||||
.Ic on ,
|
||||
the program inside the pane can request one of two modes: mode 1 which changes
|
||||
the sequence for only keys which lack an existing well-known representation; or
|
||||
@@ -4916,26 +4939,35 @@ When set to
|
||||
.Ic always ,
|
||||
modes 1 and 2 can still be requested by applications, but mode 1 will be forced
|
||||
instead of the standard mode.
|
||||
This applies only to applications inside panes; it does not force the Kitty
|
||||
keyboard protocol or change what
|
||||
.Nm
|
||||
requests from the terminal.
|
||||
When set to
|
||||
.Ic off ,
|
||||
this feature is disabled and only standard keys are reported.
|
||||
.Pp
|
||||
.Nm
|
||||
will always request extended keys itself if the terminal supports them.
|
||||
Support for the Kitty keyboard protocol is detected when a client attaches.
|
||||
See also the
|
||||
.Ic kittykeys
|
||||
and
|
||||
.Ic extkeys
|
||||
feature for the
|
||||
features for the
|
||||
.Ic terminal\-features
|
||||
option, the
|
||||
.Ic extended\-keys\-format
|
||||
option and the
|
||||
.Ic pane_key_mode
|
||||
variable.
|
||||
and
|
||||
.Ic client_key_mode
|
||||
variables.
|
||||
.It Xo Ic extended\-keys\-format
|
||||
.Op Ic csi\-u | xterm
|
||||
.Op Ic csi\-u | xterm | kitty
|
||||
.Xc
|
||||
Selects one of the two possible formats for reporting modified keys to
|
||||
applications.
|
||||
Selects the format for reporting modified keys to programs which request
|
||||
.Ic modifyOtherKeys .
|
||||
This is the equivalent of the
|
||||
.Ic formatOtherKeys
|
||||
.Xr xterm 1
|
||||
@@ -4948,6 +4980,11 @@ and as
|
||||
.Ql \[ha][[65;6u
|
||||
when set to
|
||||
.Ic csi\-u .
|
||||
.Ic kitty
|
||||
is accepted for compatibility and is the same as
|
||||
.Ic csi\-u ;
|
||||
programs which request the Kitty keyboard protocol receive it whatever this
|
||||
option is set to.
|
||||
.It Xo Ic focus\-events
|
||||
.Op Ic on | off
|
||||
.Xc
|
||||
@@ -5096,6 +5133,9 @@ Ignore function keys from
|
||||
and use the
|
||||
.Nm
|
||||
internal set only.
|
||||
.It kittykeys
|
||||
Supports the Kitty keyboard protocol.
|
||||
This is added automatically if the terminal answers the Kitty keyboard query.
|
||||
.It margins
|
||||
Supports DECSLRM margins.
|
||||
.It mouse
|
||||
@@ -5973,6 +6013,8 @@ single lines using ACS or UTF\-8 characters
|
||||
double lines using UTF\-8 characters
|
||||
.It heavy
|
||||
heavy lines using UTF\-8 characters
|
||||
.It rounded
|
||||
single lines with rounded corners using UTF\-8 characters
|
||||
.It simple
|
||||
simple ASCII characters
|
||||
.It number
|
||||
@@ -6328,7 +6370,7 @@ uses when the colour with that index is requested.
|
||||
The index may be from zero to 255.
|
||||
.Pp
|
||||
.It Xo Ic remain\-on\-exit
|
||||
.Op Ic on | off | failed | key
|
||||
.Op Ic on | off | failed | key | failed\-key
|
||||
.Xc
|
||||
A pane with this flag set is not destroyed when the program running in it
|
||||
exits.
|
||||
@@ -6338,6 +6380,10 @@ then only when the program exit status is not zero.
|
||||
If set to
|
||||
.Ic key ,
|
||||
the pane stays open and closes when a key is pressed.
|
||||
If set to
|
||||
.Ic failed\-key ,
|
||||
the pane stays open and closes when a key is pressed only if the program exit
|
||||
status is not zero.
|
||||
The pane may be reactivated with the
|
||||
.Ic respawn\-pane
|
||||
command.
|
||||
@@ -7296,6 +7342,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind"
|
||||
.It Li "client_flags" Ta "" Ta "List of client flags"
|
||||
.It Li "client_height" Ta "" Ta "Height of client"
|
||||
.It Li "client_key_mode" Ta "" Ta "Extended key reporting mode for this client"
|
||||
.It Li "client_key_table" Ta "" Ta "Current key table"
|
||||
.It Li "client_last_session" Ta "" Ta "Name of the client's last session"
|
||||
.It Li "client_name" Ta "" Ta "Name of client"
|
||||
|
||||
130
tmux.h
130
tmux.h
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tmux.h,v 1.1438 2026/09/09 08:30:05 nicm Exp $ */
|
||||
/* $OpenBSD: tmux.h,v 1.1440 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -69,7 +69,6 @@ struct options_array_item;
|
||||
struct options_entry;
|
||||
struct prompt;
|
||||
struct window_pane_prompt;
|
||||
struct redraw_damage;
|
||||
struct redraw_scene;
|
||||
struct redraw_span;
|
||||
struct screen_write_citem;
|
||||
@@ -143,6 +142,8 @@ struct winlink;
|
||||
#define VISUAL_BOTH 2
|
||||
|
||||
/* Key modifier bits. */
|
||||
#define KEYC_SUPER 0x00010000000000ULL
|
||||
#define KEYC_HYPER 0x00020000000000ULL
|
||||
#define KEYC_META 0x00100000000000ULL
|
||||
#define KEYC_CTRL 0x00200000000000ULL
|
||||
#define KEYC_SHIFT 0x00400000000000ULL
|
||||
@@ -375,6 +376,29 @@ enum {
|
||||
KEYC_F10,
|
||||
KEYC_F11,
|
||||
KEYC_F12,
|
||||
KEYC_F13,
|
||||
KEYC_F14,
|
||||
KEYC_F15,
|
||||
KEYC_F16,
|
||||
KEYC_F17,
|
||||
KEYC_F18,
|
||||
KEYC_F19,
|
||||
KEYC_F20,
|
||||
KEYC_F21,
|
||||
KEYC_F22,
|
||||
KEYC_F23,
|
||||
KEYC_F24,
|
||||
KEYC_F25,
|
||||
KEYC_F26,
|
||||
KEYC_F27,
|
||||
KEYC_F28,
|
||||
KEYC_F29,
|
||||
KEYC_F30,
|
||||
KEYC_F31,
|
||||
KEYC_F32,
|
||||
KEYC_F33,
|
||||
KEYC_F34,
|
||||
KEYC_F35,
|
||||
KEYC_IC,
|
||||
KEYC_DC,
|
||||
KEYC_HOME,
|
||||
@@ -406,6 +430,54 @@ enum {
|
||||
KEYC_KP_ENTER,
|
||||
KEYC_KP_ZERO,
|
||||
KEYC_KP_PERIOD,
|
||||
KEYC_KP_EQUAL,
|
||||
KEYC_KP_SEPARATOR,
|
||||
KEYC_KP_LEFT,
|
||||
KEYC_KP_RIGHT,
|
||||
KEYC_KP_UP,
|
||||
KEYC_KP_DOWN,
|
||||
KEYC_KP_PPAGE,
|
||||
KEYC_KP_NPAGE,
|
||||
KEYC_KP_HOME,
|
||||
KEYC_KP_END,
|
||||
KEYC_KP_IC,
|
||||
KEYC_KP_DC,
|
||||
KEYC_KP_BEGIN,
|
||||
|
||||
/* Other function keys. */
|
||||
KEYC_CAPSLOCK,
|
||||
KEYC_SCROLLLOCK,
|
||||
KEYC_NUMLOCK,
|
||||
KEYC_PRINTSCREEN,
|
||||
KEYC_PAUSE,
|
||||
KEYC_MENU,
|
||||
KEYC_MEDIA_PLAY,
|
||||
KEYC_MEDIA_PAUSE,
|
||||
KEYC_MEDIA_PLAYPAUSE,
|
||||
KEYC_MEDIA_REVERSE,
|
||||
KEYC_MEDIA_STOP,
|
||||
KEYC_MEDIA_FASTFORWARD,
|
||||
KEYC_MEDIA_REWIND,
|
||||
KEYC_MEDIA_NEXT,
|
||||
KEYC_MEDIA_PREVIOUS,
|
||||
KEYC_MEDIA_RECORD,
|
||||
KEYC_VOLUME_DOWN,
|
||||
KEYC_VOLUME_UP,
|
||||
KEYC_VOLUME_MUTE,
|
||||
KEYC_LEFT_SHIFT,
|
||||
KEYC_LEFT_CTRL,
|
||||
KEYC_LEFT_ALT,
|
||||
KEYC_LEFT_SUPER,
|
||||
KEYC_LEFT_HYPER,
|
||||
KEYC_LEFT_META,
|
||||
KEYC_RIGHT_SHIFT,
|
||||
KEYC_RIGHT_CTRL,
|
||||
KEYC_RIGHT_ALT,
|
||||
KEYC_RIGHT_SUPER,
|
||||
KEYC_RIGHT_HYPER,
|
||||
KEYC_RIGHT_META,
|
||||
KEYC_ISO_LEVEL3_SHIFT,
|
||||
KEYC_ISO_LEVEL5_SHIFT,
|
||||
|
||||
/* Theme reporting. */
|
||||
KEYC_REPORT_DARK_THEME,
|
||||
@@ -703,6 +775,16 @@ enum tty_code_code {
|
||||
#define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)
|
||||
#define EXTENDED_KEY_MODES (MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2)
|
||||
|
||||
/* Extended key output formats. */
|
||||
#define EXTENDED_KEYS_CSI_U 0
|
||||
#define EXTENDED_KEYS_XTERM 1
|
||||
#define EXTENDED_KEYS_KITTY 2
|
||||
|
||||
/* Supported Kitty keyboard protocol flags. */
|
||||
#define KITTY_KEY_DISAMBIGUATE 0x1
|
||||
#define KITTY_KEY_REPORT_ALL 0x8
|
||||
#define KITTY_KEY_SUPPORTED (KITTY_KEY_DISAMBIGUATE|KITTY_KEY_REPORT_ALL)
|
||||
|
||||
/* Mouse protocol constants. */
|
||||
#define MOUSE_PARAM_MAX 0xff
|
||||
#define MOUSE_PARAM_UTF8_MAX 0x7ff
|
||||
@@ -1069,6 +1151,11 @@ struct progress_bar {
|
||||
/* Virtual screen. */
|
||||
struct screen_sel;
|
||||
struct screen_titles;
|
||||
struct kitty_key_state {
|
||||
u_int flags;
|
||||
u_int saved_flags;
|
||||
int have_saved;
|
||||
};
|
||||
struct screen {
|
||||
char *title;
|
||||
char *path;
|
||||
@@ -1090,6 +1177,8 @@ struct screen {
|
||||
|
||||
int mode;
|
||||
int default_mode;
|
||||
struct kitty_key_state kitty_keys;
|
||||
struct kitty_key_state saved_kitty_keys;
|
||||
|
||||
u_int saved_cx;
|
||||
u_int saved_cy;
|
||||
@@ -1151,7 +1240,8 @@ enum pane_lines {
|
||||
PANE_LINES_SIMPLE,
|
||||
PANE_LINES_NUMBER,
|
||||
PANE_LINES_SPACES,
|
||||
PANE_LINES_NONE
|
||||
PANE_LINES_NONE,
|
||||
PANE_LINES_ROUNDED
|
||||
};
|
||||
|
||||
/* Pane border indicator option. */
|
||||
@@ -1340,6 +1430,7 @@ struct window_pane {
|
||||
#define PANE_CLOSEONCLICK 0x80000
|
||||
#define PANE_CAPTUREALLKEYS 0x100000
|
||||
#define PANE_FLOATOVERZOOM 0x200000
|
||||
#define PANE_CLOSEONCANCEL 0x400000
|
||||
|
||||
bitstr_t *sync_dirty;
|
||||
u_int sync_dirty_size;
|
||||
@@ -1428,7 +1519,6 @@ struct window_pane {
|
||||
TAILQ_HEAD(window_panes, window_pane);
|
||||
TAILQ_HEAD(window_panes_zindex, window_pane);
|
||||
RB_HEAD(window_pane_tree, window_pane);
|
||||
TAILQ_HEAD(redraw_damages, redraw_damage);
|
||||
|
||||
/* Window structure. */
|
||||
struct window {
|
||||
@@ -1472,9 +1562,6 @@ struct window {
|
||||
|
||||
uint64_t redraw_scene_generation;
|
||||
|
||||
struct redraw_damages damage;
|
||||
u_int damage_count;
|
||||
|
||||
struct menu_data *menu;
|
||||
u_int menu_last_px;
|
||||
u_int menu_last_py;
|
||||
@@ -1742,6 +1829,7 @@ struct tty_term {
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_INVALIDMS 0x80
|
||||
#define TERM_KITTYKEYS 0x100
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -1822,9 +1910,12 @@ struct tty {
|
||||
#define TTY_WAITBG 0x4000
|
||||
#define TTY_BRACKETPASTE 0x8000
|
||||
#define TTY_HAVESYNC 0x10000
|
||||
#define TTY_HAVEKKB 0x20000
|
||||
#define TTY_KKBPUSHED 0x40000
|
||||
#define TTY_ALL_REQUEST_FLAGS \
|
||||
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
|
||||
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC|TTY_HAVEKKB)
|
||||
int flags;
|
||||
u_int kitty_keys;
|
||||
|
||||
struct tty_term *term;
|
||||
|
||||
@@ -1847,7 +1938,7 @@ struct tty {
|
||||
};
|
||||
|
||||
/* Terminal command context. */
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int);
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
|
||||
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
||||
struct tty_ctx {
|
||||
struct screen *s;
|
||||
@@ -2977,6 +3068,7 @@ int tty_open(struct tty *, char **);
|
||||
void tty_close(struct tty *);
|
||||
void tty_free(struct tty *);
|
||||
void tty_update_features(struct tty *);
|
||||
void tty_update_kitty(struct tty *, struct screen *);
|
||||
void tty_set_selection(struct tty *, const char *, const char *, size_t);
|
||||
void tty_write(void (*)(struct tty *, const struct tty_ctx *),
|
||||
struct tty_ctx *);
|
||||
@@ -3458,6 +3550,20 @@ int input_key(struct screen *, struct bufferevent *, key_code);
|
||||
int input_key_get_mouse(struct screen *, struct mouse_event *, u_int,
|
||||
u_int, const char **, size_t *);
|
||||
|
||||
/* input-kitty.c */
|
||||
void input_kitty_reset(struct screen *);
|
||||
void input_kitty_alternate_on(struct screen *);
|
||||
void input_kitty_alternate_off(struct screen *);
|
||||
void input_kitty_set(struct screen *, u_int, int);
|
||||
void input_kitty_push(struct screen *, u_int);
|
||||
void input_kitty_pop(struct screen *, u_int);
|
||||
int input_key_kitty(struct screen *, struct bufferevent *, key_code);
|
||||
|
||||
/* tty-kitty.c */
|
||||
int tty_keys_kitty(struct tty *, const char *, size_t, size_t *,
|
||||
key_code *);
|
||||
int tty_keys_kitty_query(struct tty *, const char *, size_t, size_t *);
|
||||
|
||||
/* colour.c */
|
||||
int colour_find_rgb(u_char, u_char, u_char);
|
||||
int colour_join_rgb(u_char, u_char, u_char);
|
||||
@@ -3662,12 +3768,8 @@ void redraw_screen(struct client *);
|
||||
void redraw_pane(struct client *, struct window_pane *);
|
||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||
void redraw_free_scene(struct redraw_scene *);
|
||||
int redraw_client_has_window(struct client *, struct window *);
|
||||
void redraw_invalidate_scene(struct window *);
|
||||
void redraw_invalidate_all_scenes(void);
|
||||
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||
void redraw_free_damage(struct window *);
|
||||
void redraw_client_damage(struct client *);
|
||||
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||
|
||||
/* screen.c */
|
||||
@@ -3841,8 +3943,6 @@ int window_pane_get_pane_status(struct window_pane *);
|
||||
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
||||
u_int);
|
||||
int window_pane_is_floating(struct window_pane *);
|
||||
void window_pane_redraw_floating(struct window *,
|
||||
struct window_pane *, int, int, int, int);
|
||||
|
||||
/* window-border.c */
|
||||
void window_set_fill_cells(struct window *);
|
||||
|
||||
@@ -235,6 +235,13 @@ static const struct tty_feature tty_feature_sync = {
|
||||
0
|
||||
};
|
||||
|
||||
/* Terminal supports the Kitty keyboard protocol. */
|
||||
static const struct tty_feature tty_feature_kittykeys = {
|
||||
"kittykeys",
|
||||
NULL,
|
||||
TERM_KITTYKEYS
|
||||
};
|
||||
|
||||
/* Terminal supports extended keys. */
|
||||
static const char *const tty_feature_extkeys_capabilities[] = {
|
||||
"Eneks=\\E[>4;2m",
|
||||
@@ -386,6 +393,7 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
&tty_feature_kittykeys,
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
|
||||
27
tty-keys.c
27
tty-keys.c
@@ -772,6 +772,17 @@ tty_keys_next(struct tty *tty)
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this a Kitty keyboard protocol response? */
|
||||
switch (tty_keys_kitty_query(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
key = KEYC_UNKNOWN;
|
||||
goto complete_key;
|
||||
case -1: /* no, or not valid */
|
||||
break;
|
||||
case 1: /* partial */
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this a synchronized update mode response? */
|
||||
switch (tty_keys_sync(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
@@ -858,6 +869,18 @@ tty_keys_next(struct tty *tty)
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this an extended key press? */
|
||||
switch (tty_keys_kitty(tty, buf, len, &size, &key)) {
|
||||
case 0: /* yes */
|
||||
goto complete_key;
|
||||
case -1: /* no, or not valid */
|
||||
break;
|
||||
case -2: /* yes, but not representable by tmux */
|
||||
goto discard_key;
|
||||
case 1: /* partial */
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this an extended key press? */
|
||||
switch (tty_keys_extended_key(tty, buf, len, &size, &key)) {
|
||||
case 0: /* yes */
|
||||
@@ -1089,6 +1112,8 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
|
||||
utf8_char uc;
|
||||
|
||||
*size = 0;
|
||||
if (tty->flags & TTY_KKBPUSHED)
|
||||
return (-1);
|
||||
|
||||
/* First two bytes are always \033[. */
|
||||
if (buf[0] != '\033')
|
||||
@@ -1527,6 +1552,8 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
|
||||
}
|
||||
log_debug("%s: received primary DA %.*s", c->name, (int)*size, buf);
|
||||
|
||||
/* A DA response without a preceding keyboard response means no support. */
|
||||
tty->flags |= TTY_HAVEKKB;
|
||||
tty_update_features(tty);
|
||||
tty->flags |= TTY_HAVEDA;
|
||||
|
||||
|
||||
430
tty-kitty.c
Normal file
430
tty-kitty.c
Normal file
@@ -0,0 +1,430 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
||||
*
|
||||
* 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 <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Detect support in the outside terminal and decode its key sequences. */
|
||||
|
||||
struct tty_kitty_key {
|
||||
u_int number;
|
||||
key_code key;
|
||||
};
|
||||
|
||||
static const struct tty_kitty_key tty_kitty_keys[] = {
|
||||
{ 57358, KEYC_CAPSLOCK },
|
||||
{ 57359, KEYC_SCROLLLOCK },
|
||||
{ 57360, KEYC_NUMLOCK },
|
||||
{ 57361, KEYC_PRINTSCREEN },
|
||||
{ 57362, KEYC_PAUSE },
|
||||
{ 57363, KEYC_MENU },
|
||||
{ 57376, KEYC_F13 },
|
||||
{ 57377, KEYC_F14 },
|
||||
{ 57378, KEYC_F15 },
|
||||
{ 57379, KEYC_F16 },
|
||||
{ 57380, KEYC_F17 },
|
||||
{ 57381, KEYC_F18 },
|
||||
{ 57382, KEYC_F19 },
|
||||
{ 57383, KEYC_F20 },
|
||||
{ 57384, KEYC_F21 },
|
||||
{ 57385, KEYC_F22 },
|
||||
{ 57386, KEYC_F23 },
|
||||
{ 57387, KEYC_F24 },
|
||||
{ 57388, KEYC_F25 },
|
||||
{ 57389, KEYC_F26 },
|
||||
{ 57390, KEYC_F27 },
|
||||
{ 57391, KEYC_F28 },
|
||||
{ 57392, KEYC_F29 },
|
||||
{ 57393, KEYC_F30 },
|
||||
{ 57394, KEYC_F31 },
|
||||
{ 57395, KEYC_F32 },
|
||||
{ 57396, KEYC_F33 },
|
||||
{ 57397, KEYC_F34 },
|
||||
{ 57398, KEYC_F35 },
|
||||
{ 57399, KEYC_KP_ZERO|KEYC_KEYPAD },
|
||||
{ 57400, KEYC_KP_ONE|KEYC_KEYPAD },
|
||||
{ 57401, KEYC_KP_TWO|KEYC_KEYPAD },
|
||||
{ 57402, KEYC_KP_THREE|KEYC_KEYPAD },
|
||||
{ 57403, KEYC_KP_FOUR|KEYC_KEYPAD },
|
||||
{ 57404, KEYC_KP_FIVE|KEYC_KEYPAD },
|
||||
{ 57405, KEYC_KP_SIX|KEYC_KEYPAD },
|
||||
{ 57406, KEYC_KP_SEVEN|KEYC_KEYPAD },
|
||||
{ 57407, KEYC_KP_EIGHT|KEYC_KEYPAD },
|
||||
{ 57408, KEYC_KP_NINE|KEYC_KEYPAD },
|
||||
{ 57409, KEYC_KP_PERIOD|KEYC_KEYPAD },
|
||||
{ 57410, KEYC_KP_SLASH|KEYC_KEYPAD },
|
||||
{ 57411, KEYC_KP_STAR|KEYC_KEYPAD },
|
||||
{ 57412, KEYC_KP_MINUS|KEYC_KEYPAD },
|
||||
{ 57413, KEYC_KP_PLUS|KEYC_KEYPAD },
|
||||
{ 57414, KEYC_KP_ENTER|KEYC_KEYPAD },
|
||||
{ 57415, KEYC_KP_EQUAL|KEYC_KEYPAD },
|
||||
{ 57416, KEYC_KP_SEPARATOR|KEYC_KEYPAD },
|
||||
{ 57417, KEYC_KP_LEFT|KEYC_KEYPAD },
|
||||
{ 57418, KEYC_KP_RIGHT|KEYC_KEYPAD },
|
||||
{ 57419, KEYC_KP_UP|KEYC_KEYPAD },
|
||||
{ 57420, KEYC_KP_DOWN|KEYC_KEYPAD },
|
||||
{ 57421, KEYC_KP_PPAGE|KEYC_KEYPAD },
|
||||
{ 57422, KEYC_KP_NPAGE|KEYC_KEYPAD },
|
||||
{ 57423, KEYC_KP_HOME|KEYC_KEYPAD },
|
||||
{ 57424, KEYC_KP_END|KEYC_KEYPAD },
|
||||
{ 57425, KEYC_KP_IC|KEYC_KEYPAD },
|
||||
{ 57426, KEYC_KP_DC|KEYC_KEYPAD },
|
||||
{ 57427, KEYC_KP_BEGIN|KEYC_KEYPAD },
|
||||
{ 57428, KEYC_MEDIA_PLAY },
|
||||
{ 57429, KEYC_MEDIA_PAUSE },
|
||||
{ 57430, KEYC_MEDIA_PLAYPAUSE },
|
||||
{ 57431, KEYC_MEDIA_REVERSE },
|
||||
{ 57432, KEYC_MEDIA_STOP },
|
||||
{ 57433, KEYC_MEDIA_FASTFORWARD },
|
||||
{ 57434, KEYC_MEDIA_REWIND },
|
||||
{ 57435, KEYC_MEDIA_NEXT },
|
||||
{ 57436, KEYC_MEDIA_PREVIOUS },
|
||||
{ 57437, KEYC_MEDIA_RECORD },
|
||||
{ 57438, KEYC_VOLUME_DOWN },
|
||||
{ 57439, KEYC_VOLUME_UP },
|
||||
{ 57440, KEYC_VOLUME_MUTE },
|
||||
{ 57441, KEYC_LEFT_SHIFT },
|
||||
{ 57442, KEYC_LEFT_CTRL },
|
||||
{ 57443, KEYC_LEFT_ALT },
|
||||
{ 57444, KEYC_LEFT_SUPER },
|
||||
{ 57445, KEYC_LEFT_HYPER },
|
||||
{ 57446, KEYC_LEFT_META },
|
||||
{ 57447, KEYC_RIGHT_SHIFT },
|
||||
{ 57448, KEYC_RIGHT_CTRL },
|
||||
{ 57449, KEYC_RIGHT_ALT },
|
||||
{ 57450, KEYC_RIGHT_SUPER },
|
||||
{ 57451, KEYC_RIGHT_HYPER },
|
||||
{ 57452, KEYC_RIGHT_META },
|
||||
{ 57453, KEYC_ISO_LEVEL3_SHIFT },
|
||||
{ 57454, KEYC_ISO_LEVEL5_SHIFT }
|
||||
};
|
||||
|
||||
static int
|
||||
tty_kitty_parse_number(const char *s, u_int *value, int empty)
|
||||
{
|
||||
char *end;
|
||||
unsigned long number;
|
||||
|
||||
if (*s == '\0') {
|
||||
if (!empty)
|
||||
return (-1);
|
||||
return (0);
|
||||
}
|
||||
if (!isdigit((u_char)*s))
|
||||
return (-1);
|
||||
number = strtoul(s, &end, 10);
|
||||
if (*end != '\0' || number > UINT_MAX)
|
||||
return (-1);
|
||||
*value = number;
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tty_kitty_parse_list(char *s, u_int *first, u_int *second, int empty,
|
||||
u_int maximum)
|
||||
{
|
||||
char *copy, *item;
|
||||
u_int n = 0, value;
|
||||
int result = 0;
|
||||
|
||||
copy = s;
|
||||
while ((item = strsep(©, ":")) != NULL) {
|
||||
if (maximum != 0 && n == maximum)
|
||||
return (-1);
|
||||
value = 0;
|
||||
if (tty_kitty_parse_number(item, &value, empty) != 0) {
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
if (n == 0 && *item != '\0')
|
||||
*first = value;
|
||||
else if (n == 1 && *item != '\0')
|
||||
*second = value;
|
||||
n++;
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
static int
|
||||
tty_kitty_find_end(const char *buf, size_t len, size_t *end)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 2; i < len; i++) {
|
||||
if (buf[i] == 'u' || buf[i] == '~' || buf[i] == 'A' ||
|
||||
buf[i] == 'B' || buf[i] == 'C' || buf[i] == 'D' ||
|
||||
buf[i] == 'E' || buf[i] == 'F' || buf[i] == 'H' ||
|
||||
buf[i] == 'P' || buf[i] == 'Q' || buf[i] == 'S') {
|
||||
*end = i;
|
||||
return (0);
|
||||
}
|
||||
if (!isdigit((u_char)buf[i]) && buf[i] != ';' && buf[i] != ':')
|
||||
return (-1);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
static key_code
|
||||
tty_kitty_function(u_int number, char final)
|
||||
{
|
||||
u_int i;
|
||||
|
||||
if (final == 'u') {
|
||||
switch (number) {
|
||||
case 9:
|
||||
return (C0_HT);
|
||||
case 13:
|
||||
return (C0_CR);
|
||||
case 27:
|
||||
return (C0_ESC);
|
||||
case 127:
|
||||
return (KEYC_BSPACE);
|
||||
}
|
||||
for (i = 0; i < nitems(tty_kitty_keys); i++) {
|
||||
if (tty_kitty_keys[i].number == number)
|
||||
return (tty_kitty_keys[i].key);
|
||||
}
|
||||
return (KEYC_NONE);
|
||||
}
|
||||
|
||||
if (final == '~') {
|
||||
switch (number) {
|
||||
case 2: return (KEYC_IC);
|
||||
case 3: return (KEYC_DC);
|
||||
case 5: return (KEYC_PPAGE);
|
||||
case 6: return (KEYC_NPAGE);
|
||||
case 7: return (KEYC_HOME);
|
||||
case 8: return (KEYC_END);
|
||||
case 11: return (KEYC_F1);
|
||||
case 12: return (KEYC_F2);
|
||||
case 13: return (KEYC_F3);
|
||||
case 14: return (KEYC_F4);
|
||||
case 15: return (KEYC_F5);
|
||||
case 17: return (KEYC_F6);
|
||||
case 18: return (KEYC_F7);
|
||||
case 19: return (KEYC_F8);
|
||||
case 20: return (KEYC_F9);
|
||||
case 21: return (KEYC_F10);
|
||||
case 23: return (KEYC_F11);
|
||||
case 24: return (KEYC_F12);
|
||||
case 29: return (KEYC_MENU);
|
||||
case 57427: return (KEYC_KP_BEGIN|KEYC_KEYPAD);
|
||||
}
|
||||
return (KEYC_UNKNOWN);
|
||||
}
|
||||
if (number != 1)
|
||||
return (KEYC_UNKNOWN);
|
||||
switch (final) {
|
||||
case 'A': return (KEYC_UP|KEYC_CURSOR);
|
||||
case 'B': return (KEYC_DOWN|KEYC_CURSOR);
|
||||
case 'C': return (KEYC_RIGHT|KEYC_CURSOR);
|
||||
case 'D': return (KEYC_LEFT|KEYC_CURSOR);
|
||||
case 'E': return (KEYC_KP_BEGIN|KEYC_KEYPAD);
|
||||
case 'F': return (KEYC_END);
|
||||
case 'H': return (KEYC_HOME);
|
||||
case 'P': return (KEYC_F1);
|
||||
case 'Q': return (KEYC_F2);
|
||||
case 'S': return (KEYC_F4);
|
||||
}
|
||||
return (KEYC_UNKNOWN);
|
||||
}
|
||||
|
||||
int
|
||||
tty_keys_kitty_query(struct tty *tty, const char *buf, size_t len,
|
||||
size_t *size)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
size_t i;
|
||||
u_int flags = 0;
|
||||
|
||||
*size = 0;
|
||||
if (tty->flags & TTY_HAVEKKB)
|
||||
return (-1);
|
||||
|
||||
if (buf[0] != '\033')
|
||||
return (-1);
|
||||
if (len == 1)
|
||||
return (1);
|
||||
if (buf[1] != '[')
|
||||
return (-1);
|
||||
if (len == 2)
|
||||
return (1);
|
||||
if (buf[2] != '?')
|
||||
return (-1);
|
||||
if (len == 3)
|
||||
return (1);
|
||||
|
||||
for (i = 3; i < len && isdigit((u_char)buf[i]); i++) {
|
||||
if (flags > (UINT_MAX - (buf[i] - '0')) / 10)
|
||||
return (-1);
|
||||
flags = (flags * 10) + (buf[i] - '0');
|
||||
}
|
||||
if (i == len)
|
||||
return (1);
|
||||
if (i == 3 || buf[i] != 'u')
|
||||
return (-1);
|
||||
|
||||
*size = i + 1;
|
||||
tty->kitty_keys = flags;
|
||||
tty->flags |= TTY_HAVEKKB;
|
||||
log_debug("%s: received Kitty keyboard flags %u", c->name, flags);
|
||||
tty_parse_client_features(c, "kittykeys", ",");
|
||||
tty_update_features(tty);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
tty_update_kitty(struct tty *tty, struct screen *s)
|
||||
{
|
||||
u_int flags;
|
||||
char buf[32];
|
||||
|
||||
if (options_get_number(global_options, "extended-keys") == 0 ||
|
||||
(~tty->term->flags & TERM_KITTYKEYS)) {
|
||||
if (tty->flags & TTY_KKBPUSHED) {
|
||||
tty_puts(tty, "\033[<u");
|
||||
tty->flags &= ~TTY_KKBPUSHED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
flags = KITTY_KEY_DISAMBIGUATE;
|
||||
if (s != NULL && (s->kitty_keys.flags & KITTY_KEY_SUPPORTED) != 0)
|
||||
flags = s->kitty_keys.flags & KITTY_KEY_SUPPORTED;
|
||||
if ((tty->flags & TTY_KKBPUSHED) == 0) {
|
||||
xsnprintf(buf, sizeof buf, "\033[>%uu", flags);
|
||||
tty_puts(tty, buf);
|
||||
tty->flags |= TTY_KKBPUSHED;
|
||||
} else if (tty->kitty_keys != flags) {
|
||||
xsnprintf(buf, sizeof buf, "\033[=%uu", flags);
|
||||
tty_puts(tty, buf);
|
||||
}
|
||||
tty->kitty_keys = flags;
|
||||
}
|
||||
|
||||
int
|
||||
tty_keys_kitty(struct tty *tty, const char *buf, size_t len, size_t *size,
|
||||
key_code *key)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
struct utf8_data ud;
|
||||
utf8_char uc;
|
||||
size_t end;
|
||||
char tmp[128], *copy, *field, final;
|
||||
u_int number = 0, modifiers = 1, event = 1, value;
|
||||
u_int fields = 0;
|
||||
key_code nkey, onlykey;
|
||||
int result;
|
||||
|
||||
*size = 0;
|
||||
if ((tty->flags & TTY_KKBPUSHED) == 0)
|
||||
return (-1);
|
||||
if (len == 0 || buf[0] != '\033')
|
||||
return (-1);
|
||||
if (len == 1)
|
||||
return (1);
|
||||
if (buf[1] != '[')
|
||||
return (-1);
|
||||
if (len == 2)
|
||||
return (1);
|
||||
if (buf[2] == '?')
|
||||
return (-1);
|
||||
|
||||
result = tty_kitty_find_end(buf, len, &end);
|
||||
if (result != 0)
|
||||
return (result);
|
||||
if (end - 2 >= sizeof tmp)
|
||||
return (-1);
|
||||
memcpy(tmp, buf + 2, end - 2);
|
||||
tmp[end - 2] = '\0';
|
||||
final = buf[end];
|
||||
*size = end + 1;
|
||||
|
||||
copy = tmp;
|
||||
while ((field = strsep(©, ";")) != NULL) {
|
||||
if (fields == 3)
|
||||
return (-1);
|
||||
value = 0;
|
||||
if (fields == 0) {
|
||||
if (*field == '\0') {
|
||||
if (final == 'u')
|
||||
return (-1);
|
||||
number = 1;
|
||||
} else if (tty_kitty_parse_list(field, &number, &value,
|
||||
1, 3) != 0)
|
||||
return (-1);
|
||||
} else if (fields == 1) {
|
||||
if (tty_kitty_parse_list(field, &modifiers, &event, 1,
|
||||
2) != 0)
|
||||
return (-1);
|
||||
} else if (tty_kitty_parse_list(field, &value, &value, 1, 0) != 0)
|
||||
return (-1);
|
||||
fields++;
|
||||
}
|
||||
if (fields == 0 || number == 0 || modifiers == 0)
|
||||
return (-1);
|
||||
if (event == 0 || event == 3 || event > 3)
|
||||
return (-2);
|
||||
|
||||
modifiers--;
|
||||
if (modifiers & 32)
|
||||
return (-2);
|
||||
nkey = tty_kitty_function(number, final);
|
||||
if (nkey == KEYC_NONE) {
|
||||
if (final != 'u')
|
||||
return (-1);
|
||||
if (number >= 57344 && number <= 63743)
|
||||
return (-2);
|
||||
if (number <= 0x7f)
|
||||
nkey = number;
|
||||
else if (utf8_fromwc(number, &ud) != UTF8_DONE ||
|
||||
utf8_from_data(&ud, &uc) != UTF8_DONE)
|
||||
return (-2);
|
||||
else
|
||||
nkey = uc;
|
||||
} else if (nkey == KEYC_UNKNOWN)
|
||||
return (-1);
|
||||
|
||||
if (modifiers & 1)
|
||||
nkey |= KEYC_SHIFT;
|
||||
if (modifiers & 2)
|
||||
nkey |= KEYC_META|KEYC_IMPLIED_META;
|
||||
if (modifiers & 4)
|
||||
nkey |= KEYC_CTRL;
|
||||
if (modifiers & 8)
|
||||
nkey |= KEYC_SUPER;
|
||||
if (modifiers & 16)
|
||||
nkey |= KEYC_HYPER;
|
||||
|
||||
/* Shift-Tab has a dedicated tmux key code. */
|
||||
onlykey = nkey & KEYC_MASK_KEY;
|
||||
if (onlykey == C0_HT && (nkey & KEYC_SHIFT))
|
||||
nkey = KEYC_BTAB|(nkey & ~KEYC_MASK_KEY & ~KEYC_SHIFT);
|
||||
|
||||
if (log_get_level() != 0) {
|
||||
log_debug("%s: Kitty key %.*s is %llx (%s)", c->name,
|
||||
(int)*size, buf, nkey, key_string_lookup_key(nkey, 1));
|
||||
}
|
||||
*key = nkey;
|
||||
return (0);
|
||||
}
|
||||
18
tty.c
18
tty.c
@@ -409,6 +409,8 @@ tty_send_requests(struct tty *tty)
|
||||
return;
|
||||
|
||||
if (tty->term->flags & TERM_VT100LIKE) {
|
||||
if (~tty->flags & TTY_HAVEKKB)
|
||||
tty_puts(tty, "\033[?u");
|
||||
if (~tty->flags & TTY_HAVEDA)
|
||||
tty_puts(tty, "\033[c");
|
||||
if (~tty->flags & TTY_HAVEDA2)
|
||||
@@ -507,6 +509,10 @@ tty_stop_tty(struct tty *tty)
|
||||
tty_raw(tty, "\033[?7727l");
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
|
||||
if (tty->flags & TTY_KKBPUSHED) {
|
||||
tty_raw(tty, "\033[<u");
|
||||
tty->flags &= ~TTY_KKBPUSHED;
|
||||
}
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
|
||||
@@ -559,8 +565,12 @@ tty_update_features(struct tty *tty)
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_putcode(tty, TTYC_ENMG);
|
||||
if (options_get_number(global_options, "extended-keys"))
|
||||
if (options_get_number(global_options, "extended-keys") == 0 ||
|
||||
(tty->term->flags & TERM_KITTYKEYS))
|
||||
tty_puts(tty, tty_term_string(tty->term, TTYC_DSEKS));
|
||||
else
|
||||
tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
|
||||
tty_update_kitty(tty, NULL);
|
||||
if (options_get_number(global_options, "focus-events"))
|
||||
tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
|
||||
if (tty->term->flags & TERM_VT100LIKE)
|
||||
@@ -1120,7 +1130,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx)
|
||||
*/
|
||||
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
||||
log_debug("%s: %s large region redraw", __func__, c->name);
|
||||
ctx->redraw_cb(ctx, ctx->orupper, ctx->orlower - ctx->orupper + 1);
|
||||
ctx->redraw_cb(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2032,7 +2042,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
||||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
c->overlay_check != NULL) {
|
||||
ctx->redraw_cb(ctx, 0, ctx->sy);
|
||||
ctx->redraw_cb(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2114,7 +2124,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty->cy == tty->rlower)
|
||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||
else
|
||||
ctx->redraw_cb(ctx, ctx->ocy, 1);
|
||||
ctx->redraw_cb(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
|
||||
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -120,6 +120,10 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
|
||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
||||
break;
|
||||
case PANE_LINES_ROUNDED:
|
||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||
utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
|
||||
break;
|
||||
case PANE_LINES_SIMPLE:
|
||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
||||
|
||||
126
window.c
126
window.c
@@ -415,7 +415,6 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
|
||||
TAILQ_INIT(&w->panes);
|
||||
TAILQ_INIT(&w->z_index);
|
||||
TAILQ_INIT(&w->last_panes);
|
||||
TAILQ_INIT(&w->damage);
|
||||
w->active = NULL;
|
||||
|
||||
w->lastlayout = -1;
|
||||
@@ -461,7 +460,6 @@ window_destroy(struct window *w)
|
||||
|
||||
menu_destroy(w);
|
||||
window_destroy_panes(w);
|
||||
redraw_free_damage(w);
|
||||
|
||||
if (event_initialized(&w->name_event))
|
||||
evtimer_del(&w->name_event);
|
||||
@@ -713,7 +711,6 @@ int
|
||||
window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
{
|
||||
struct window_pane *lastwp;
|
||||
int unzoomed;
|
||||
|
||||
log_debug("%s: pane %%%u", __func__, wp->id);
|
||||
|
||||
@@ -721,8 +718,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
return (0);
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
return (0);
|
||||
unzoomed = (w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp);
|
||||
if (unzoomed)
|
||||
if ((w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp))
|
||||
window_unzoom(w, 1);
|
||||
lastwp = w->active;
|
||||
|
||||
@@ -739,19 +735,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
}
|
||||
|
||||
tty_update_window_offset(w);
|
||||
|
||||
/*
|
||||
* Unzooming changes every pane's geometry and needs a full window
|
||||
* redraw. Otherwise, only the previous and new active pane's border
|
||||
* and status appearance changed, so avoid redrawing unaffected pane
|
||||
* content.
|
||||
*/
|
||||
if (unzoomed)
|
||||
server_redraw_window(w);
|
||||
else {
|
||||
server_redraw_window_borders(w);
|
||||
server_status_window(w);
|
||||
}
|
||||
server_redraw_window(w);
|
||||
|
||||
if (notify)
|
||||
window_fire_pane_changed(w, w->active, lastwp);
|
||||
@@ -2922,109 +2906,3 @@ window_pane_is_floating(struct window_pane *wp)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for a floating pane's rectangle, grown by one cell on every
|
||||
* side - a floating pane draws its border frame at xoff-1/yoff-1 through
|
||||
* xoff+sx/yoff+sy (see the "floating" case in screen-redraw.c), one cell
|
||||
* outside its own content area, so damage for just the content area leaves
|
||||
* the frame's previous position undrawn as the pane moves. If a scrollbar
|
||||
* is reserved, its side of the frame is pushed out further still by its
|
||||
* width and padding (also matched in screen-redraw.c), so grow that side
|
||||
* to match.
|
||||
*/
|
||||
static void
|
||||
window_pane_damage_floating(struct window *w, struct window_pane *wp,
|
||||
int xoff, int yoff, int sx, int sy)
|
||||
{
|
||||
int x0, x1, y0, y1, sb_left = 0, sb_right = 0;
|
||||
|
||||
if (window_pane_scrollbar_reserve(wp)) {
|
||||
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||
sb_left = wp->scrollbar_style.width +
|
||||
wp->scrollbar_style.pad;
|
||||
else
|
||||
sb_right = wp->scrollbar_style.width +
|
||||
wp->scrollbar_style.pad;
|
||||
}
|
||||
|
||||
x0 = xoff - 1 - sb_left;
|
||||
x1 = xoff + sx + sb_right;
|
||||
y0 = yoff - 1;
|
||||
y1 = yoff + sy;
|
||||
if (x0 < 0)
|
||||
x0 = 0;
|
||||
if (y0 < 0)
|
||||
y0 = 0;
|
||||
if (x1 < x0 || y1 < y0)
|
||||
return;
|
||||
redraw_damage_window(w, (u_int)x0, (u_int)y0, (u_int)(x1 - x0) + 1,
|
||||
(u_int)(y1 - y0) + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether a pane's scrollbar strip - not its whole body - intersects a
|
||||
* window-coordinate rectangle. A reserved scrollbar occupies a strip of
|
||||
* scrollbar_style.width+pad columns just outside the pane's own content
|
||||
* area (see the scrollbar-reserve case in layout_fix_panes(), layout.c),
|
||||
* on whichever side w->sb_pos points to.
|
||||
*/
|
||||
static int
|
||||
window_pane_scrollbar_intersects(struct window *w, struct window_pane *wp,
|
||||
u_int x, u_int y, u_int sx, u_int sy)
|
||||
{
|
||||
int sb_x, sb_w, ix = (int)x, iy = (int)y, isx = (int)sx;
|
||||
int isy = (int)sy;
|
||||
|
||||
if (!window_pane_scrollbar_reserve(wp))
|
||||
return (0);
|
||||
sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
|
||||
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||
sb_x = (int)wp->xoff - sb_w;
|
||||
else
|
||||
sb_x = (int)wp->xoff + (int)wp->sx;
|
||||
|
||||
return (sb_x < ix + isx && sb_x + sb_w > ix &&
|
||||
(int)wp->yoff < iy + isy && (int)wp->yoff + (int)wp->sy > iy);
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for only a floating pane's old and new area, rather than
|
||||
* the whole window - a floating pane move or resize only disturbs what it
|
||||
* was covering and what it now covers. Scrollbars aren't covered by the
|
||||
* damage system, so a pane whose *scrollbar strip* (not its whole body)
|
||||
* intersects either area is still flagged directly for a scrollbar redraw.
|
||||
* Checking the whole pane body here, rather than just its narrow scrollbar
|
||||
* strip, meant merely dragging over a pane's ordinary content set
|
||||
* PANE_REDRAWSCROLLBAR on every such pane on every motion event, triggering
|
||||
* a needless scrollbar redraw (and the redraw pass it forces) each time
|
||||
* even though the scrollbar itself never moved.
|
||||
*
|
||||
* Shared by every command that drags a floating pane around by the mouse:
|
||||
* resize-pane's own border drag (cmd-resize-pane.c), move-pane -M's
|
||||
* alternate Alt-drag (cmd-join-pane.c), and split-window/new-pane's
|
||||
* interactive resize of a newly-created floating pane (cmd-split-window.c).
|
||||
*/
|
||||
void
|
||||
window_pane_redraw_floating(struct window *w, struct window_pane *wp,
|
||||
int old_xoff, int old_yoff, int old_sx, int old_sy)
|
||||
{
|
||||
struct window_pane *loop;
|
||||
|
||||
window_pane_damage_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
window_pane_damage_floating(w, wp, wp->xoff, wp->yoff, wp->sx,
|
||||
wp->sy);
|
||||
|
||||
TAILQ_FOREACH(loop, &w->panes, entry) {
|
||||
if (window_pane_scrollbar_intersects(w, loop,
|
||||
(u_int)old_xoff, (u_int)old_yoff, (u_int)old_sx,
|
||||
(u_int)old_sy) ||
|
||||
window_pane_scrollbar_intersects(w, loop, wp->xoff,
|
||||
wp->yoff, wp->sx, wp->sy))
|
||||
loop->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
|
||||
/* Session status formats may depend on the pane's new geometry. */
|
||||
server_status_window(w);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user