diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 8d85c57b2..dcc5a6af4 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-break-pane.c,v 1.72 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-break-pane.c,v 1.73 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -98,9 +98,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) struct session *src_s = source->s; struct session *dst_s = target->s; struct window_pane *wp = source->wp; - struct window *w = wl->window; + struct window *w = wl->window, *old_w = w; char *cause, *cp; - int idx = target->idx, before; + int idx = target->idx, before, old_idx = wl->idx; const char *template, *name = args_get(args, 'n'); if (args_has(args, 'W')) @@ -137,6 +137,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) wl = winlink_find_by_window(&dst_s->windows, w); if (wl == NULL) return (CMD_RETURN_ERROR); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); goto out; } if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { @@ -175,6 +176,8 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) idx = -1 - options_get_number(dst_s->options, "base-index"); wl = session_attach(dst_s, w, idx, &cause); /* can't fail */ window_remove_ref(w, __func__); + events_fire_window("window-created", w); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); if (!args_has(args, 'd')) { session_select(dst_s, wl->idx); cmd_find_from_session(current, dst_s, 0); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index bbee45b96..659f3bee0 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.69 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.71 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -458,6 +458,13 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "size or position %s", cause); @@ -498,6 +505,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) } else server_status_session(dst_s); + window_fire_pane_moved(src_wp, src_w, src_wl->idx, dst_w, dst_idx); if (window_count_panes(src_w, 1) == 0) server_kill_window(src_w, 1); else diff --git a/cmd-split-window.c b/cmd-split-window.c index 6c657fab5..5ef5425fb 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.141 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.142 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -61,8 +61,8 @@ const struct cmd_entry cmd_split_window_entry = { .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " - "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " " - "[shell-command [argument ...]]", + "[-R inactive-border-style] [-T title] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -95,14 +95,23 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_new_pane_entry) is_floating = !args_has(args, 'L'); - else + else { is_floating = window_pane_is_floating(wp); + flags |= SPAWN_SPLIT; + } - flags = is_floating ? SPAWN_FLOATING : 0; + if (is_floating) + flags |= SPAWN_FLOATING; + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; + if (args_has(args, 'd')) + flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + flags |= SPAWN_ZOOM; input = args_has(args, 'I'); if (input || (count == 1 && *args_string(args, 0) == '\0')) @@ -130,9 +139,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } } - if (is_floating) - lc = layout_get_floating_cell(item, args, lines, w, wp, &cause); - else + if (flags & SPAWN_FLOATING) { + lc = layout_get_floating_cell(item, args, lines, w, wp, flags, + &cause); + } else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "%s", cause); @@ -158,12 +168,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) sc.idx = -1; sc.cwd = args_get(args, 'c'); - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { cmdq_error(item, "create pane failed: %s", cause); @@ -239,10 +244,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) break; } } - if (!args_has(args, 'd')) + if (~flags & SPAWN_DETACHED) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - if (!is_floating) { + if (~flags & SPAWN_FLOATING) { window_pop_zoom(wp->window); server_redraw_window(wp->window); } diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index da7ea3706..b2e53f315 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.53 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.54 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -68,11 +68,14 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *tmp_wp, *src_wp, *dst_wp; struct layout_cell *src_lc, *dst_lc; u_int sx, sy, xoff, yoff; + int src_idx, dst_idx; dst_w = target->wl->window; dst_wp = target->wp; + dst_idx = target->wl->idx; src_w = source->wl->window; src_wp = source->wp; + src_idx = source->wl->idx; if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) server_redraw_window(dst_w); @@ -180,6 +183,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) redraw_invalidate_scene(dst_w); server_redraw_window(dst_w); + if (src_w != dst_w) { + window_fire_pane_moved(src_wp, src_w, src_idx, dst_w, dst_idx); + window_fire_pane_moved(dst_wp, dst_w, dst_idx, src_w, src_idx); + } events_fire_window("window-layout-changed", src_w); if (src_w != dst_w) events_fire_window("window-layout-changed", dst_w); diff --git a/cmd.c b/cmd.c index e16008f71..30558b31f 100644 --- a/cmd.c +++ b/cmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd.c,v 1.186 2026/07/12 20:35:52 nicm Exp $ */ +/* $OpenBSD: cmd.c,v 1.187 2026/07/13 09:37:39 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -871,7 +871,7 @@ cmd_template_replace(const char *template, const char *s, int idx) ptr++; slen = strlen(s); - if (slen > SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) + if (slen >= SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) fatalx("argument too long"); buf = xrealloc(buf, len + (slen * 3) + 1); for (cp = s; *cp != '\0'; cp++) { diff --git a/input.c b/input.c index 0ccef3b11..0c1de62b6 100644 --- a/input.c +++ b/input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input.c,v 1.266 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: input.c,v 1.267 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1316,8 +1316,10 @@ input_c0_dispatch(struct input_ctx *ictx) case '\000': /* NUL */ break; case '\007': /* BEL */ - if (wp != NULL) + if (wp != NULL) { + events_fire_pane("pane-bell", wp); alerts_queue(wp->window, WINDOW_BELL); + } break; case '\010': /* BS */ screen_write_backspace(sctx); @@ -2199,7 +2201,7 @@ input_csi_dispatch_winops(struct input_ctx *ictx) screen_pop_title(sctx->s); if (wp == NULL) break; - events_fire_pane("pane-title-changed", wp); + input_fire_pane_title_changed(wp, sctx->s->title); server_redraw_window_borders(w); server_status_window(w); break; diff --git a/layout-set.c b/layout-set.c index 7e944d32f..65cb38918 100644 --- a/layout-set.c +++ b/layout-set.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout-set.c,v 1.39 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout-set.c,v 1.40 2026/07/13 09:42:12 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -135,6 +135,21 @@ layout_set_first_tiled(struct window *w) return (NULL); } +static void +layout_set_link_floating(struct window *w, struct layout_cell *lcroot) +{ + struct window_pane *wp; + struct layout_cell *lc; + + TAILQ_FOREACH(wp, &w->panes, entry) { + lc = wp->layout_cell; + if (!layout_cell_is_tiled(lc)) { + TAILQ_INSERT_TAIL(&lcroot->cells, lc, entry); + lc->parent = lcroot; + } + } +} + static void layout_set_even(struct window *w, enum layout_type type) { @@ -267,6 +282,8 @@ layout_set_main_h(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -364,6 +381,8 @@ layout_set_main_h_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -461,6 +480,8 @@ layout_set_main_v(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -559,6 +580,8 @@ layout_set_main_v_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -700,6 +723,7 @@ layout_set_tiled(struct window *w) w->sy - used); } + layout_set_link_floating(w, lcroot); layout_fix_offsets(w); layout_fix_panes(w, NULL); diff --git a/layout.c b/layout.c index 177d2f4cf..bddad3ab8 100644 --- a/layout.c +++ b/layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout.c,v 1.93 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout.c,v 1.94 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -1598,7 +1598,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct window *w, struct window_pane *wp, int flags, char **cause) { struct layout_cell *lc; - enum layout_type type; + enum layout_type type = LAYOUT_TOPBOTTOM; u_int curval; int size = -1; char *error = NULL; @@ -1608,12 +1608,11 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - type = LAYOUT_TOPBOTTOM; - if (args_has(args, 'h')) + if (flags & SPAWN_HORIZONTAL) type = LAYOUT_LEFTRIGHT; if (args_has(args, 'l') || args_has(args, 'p')) { - if (args_has(args, 'f')) { + if (flags & SPAWN_FULLSIZE) { if (type == LAYOUT_TOPBOTTOM) curval = w->sy; else @@ -1641,12 +1640,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) *cause = xstrdup("no space for a new pane"); @@ -1656,17 +1650,24 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - enum pane_lines lines, struct window *w, struct window_pane *wp, + enum pane_lines lines, struct window *w, struct window_pane *wp, int flags, char **cause) { - struct layout_cell *lcnew; + struct layout_cell *lcnew, *lc = wp->layout_cell; struct layout_geometry fg; layout_geometry_init(&fg); - if (layout_floating_args_parse(item, args, lines, w, &fg, cause) != 0) - return (NULL); + if (flags & SPAWN_SPLIT) { + if (layout_split_floating_cell(lc, w, &fg, lines, flags, cause) + != 0) + return (NULL); + } else { + if (layout_floating_args_parse(item, args, lines, w, &fg, cause) + != 0) + return (NULL); + } - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lcnew = layout_floating_pane(w, wp, &fg); return (lcnew); } @@ -1766,6 +1767,131 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args, return (0); } +int +layout_split_floating_cell(struct layout_cell *lc, struct window *w, + struct layout_geometry *out, enum pane_lines lines, int flags, + char **cause) +{ + struct layout_geometry old, new; + int tborder = 1, bborder = w->sy - 1; + int lborder = 3, rborder = w->sx - 3; + int border = lines != PANE_LINES_NONE ? 1 : 0; + int size, space; + + /* First, move the target cell in-bounds. */ + memcpy(&old, &lc->g, sizeof old); + if (lborder > old.xoff - border) + old.xoff = lborder + border; + if (rborder < old.xoff + (int)old.sx + border) + old.xoff = rborder - (int)old.sx - border; + if (tborder > old.yoff - border) + old.yoff = tborder + border; + if (bborder < old.yoff + (int)old.sy + border) + old.yoff = bborder - (int)old.sy - border; + + /* Move the new cell to its ideal position. */ + memcpy(&new, &old, sizeof new); + if (flags & SPAWN_HORIZONTAL) { + if (flags & SPAWN_BEFORE) + new.xoff -= old.sx + 2 * border; + else + new.xoff += old.sx + 2 * border; + } else { + if (flags & SPAWN_BEFORE) + new.yoff -= old.sy + 2 * border; + else + new.yoff += old.sy + 2 * border; + } + + /* + * The position of the new cell is checked to see if it is in bounds. + * If it isn't, the availible space is split and equally given to both + * cells. Only one border is check because the target cell is in bounds + * already. + */ + if (lborder > new.xoff - border) { + /* + * The space for both panes is calculated. Since the offsets are + * associated to where pane contents start, we remove pane + * borders from the space. '1' is added in case the space is + * odd. + */ + space = old.xoff + old.sx - lborder - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = lborder + border; + old.xoff = new.xoff + new.sx + 2 * border; + /* + * If the original space was to be odd (now even), subtract 1 + * from the rightmost cell + */ + if (space % 2 == 0) + old.sx -= 1; + } else if (rborder < new.xoff + (int)new.sx + border) { + space = rborder - old.xoff - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = old.xoff + old.sx + 2 * border; + if (space % 2 == 0) + new.sx -= 1; + } else if (tborder > new.yoff - border) { + space = old.sy + old.yoff - tborder - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = tborder + border; + old.yoff = new.yoff + new.sy + 2 * border; + if (space % 2 == 0) + old.sy -= 1; + } else if (bborder < new.yoff + (int)new.sy + border) { + space = bborder - old.yoff - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = old.yoff + old.sy + 2 * border; + if (space % 2 == 0) + new.sy -= 1; + } + + /* + * Expand the cell to occupy the whole availible space where it was + * spawned. + */ + if (flags & SPAWN_FULLSIZE) { + if (flags & SPAWN_HORIZONTAL) { + new.yoff = tborder + border; + new.sy = bborder - tborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.xoff = lborder + border; + new.sx = old.xoff - new.xoff - 2 * border; + } else { + new.sx = rborder - new.xoff - border; + } + } else { + new.xoff = lborder + border; + new.sx = rborder - lborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.yoff = tborder + border; + new.sy = old.yoff - new.yoff - 2 * border; + } else { + new.sy = bborder - new.yoff - border; + } + } + } + + if (new.sx < PANE_MINIMUM || new.sy < PANE_MINIMUM || + old.sx < PANE_MINIMUM || old.sy < PANE_MINIMUM) { + *cause = xstrdup("no space for a new pane"); + return (-1); + } + + layout_set_size(lc, old.sx, old.sy, old.xoff, old.yoff); + memcpy(out, &new, sizeof *out); + return (0); +} + /* * Removes a cell from the tiled layout by giving the cell's space to the * nearest neighbour. diff --git a/options-table.c b/options-table.c index 55f169683..9d38a2e83 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.233 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.234 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1924,6 +1924,8 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("alert-silence", ""), OPTIONS_TABLE_HOOK("client-active", ""), OPTIONS_TABLE_HOOK("client-attached", ""), + OPTIONS_TABLE_HOOK("client-created", ""), + OPTIONS_TABLE_HOOK("client-closed", ""), OPTIONS_TABLE_HOOK("client-detached", ""), OPTIONS_TABLE_HOOK("client-focus-in", ""), OPTIONS_TABLE_HOOK("client-focus-out", ""), @@ -1933,6 +1935,7 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("client-dark-theme", ""), OPTIONS_TABLE_HOOK("command-error", ""), OPTIONS_TABLE_HOOK("marked-pane-changed", ""), + OPTIONS_TABLE_PANE_HOOK("pane-bell", ""), OPTIONS_TABLE_PANE_HOOK("pane-command-finished", ""), OPTIONS_TABLE_PANE_HOOK("pane-command-started", ""), OPTIONS_TABLE_PANE_HOOK("pane-created", ""), @@ -1943,6 +1946,7 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", ""), OPTIONS_TABLE_PANE_HOOK("pane-mode-entered", ""), OPTIONS_TABLE_PANE_HOOK("pane-mode-exited", ""), + OPTIONS_TABLE_PANE_HOOK("pane-moved", ""), OPTIONS_TABLE_PANE_HOOK("pane-prompt-closed", ""), OPTIONS_TABLE_PANE_HOOK("pane-prompt-opened", ""), OPTIONS_TABLE_PANE_HOOK("pane-resized", ""), @@ -1951,8 +1955,12 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_PANE_HOOK("pane-title-changed", ""), OPTIONS_TABLE_HOOK("session-closed", ""), OPTIONS_TABLE_HOOK("session-created", ""), + OPTIONS_TABLE_HOOK("session-added-to-group", ""), OPTIONS_TABLE_HOOK("session-renamed", ""), + OPTIONS_TABLE_HOOK("session-removed-from-group", ""), OPTIONS_TABLE_HOOK("session-window-changed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-created", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-closed", ""), OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", ""), OPTIONS_TABLE_HOOK("window-linked", ""), OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), diff --git a/prompt.c b/prompt.c index 6187a7a12..c12a249a9 100644 --- a/prompt.c +++ b/prompt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: prompt.c,v 1.4 2026/06/26 14:40:30 nicm Exp $ */ +/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -42,6 +42,8 @@ struct prompt { char *word_separators; struct grid_cell style; struct grid_cell command_style; + char *style_str; + char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; @@ -61,11 +63,30 @@ struct prompt { char *complete_display; }; +struct prompt_layout { + u_int area_x; + u_int area_width; + + u_int content_x; + u_int content_width; + + u_int label_width; + u_int input_x; + u_int cursor_x; + + u_int input_offset; + u_int input_width; +}; + static char *prompt_complete(struct prompt *, const char *, u_int); static void prompt_clear_complete(struct prompt *); -static char *prompt_expand(struct prompt *); +static struct format_tree *prompt_format_tree(struct prompt *); +static char *prompt_expand1(struct prompt *, struct format_tree *); +static void prompt_effective_style(struct prompt *, struct style *, + struct format_tree *); +static void prompt_layout(struct prompt *, u_int, u_int, + struct prompt_layout *, char **, struct style *); static int prompt_replace_complete(struct prompt *, const char *); -static u_int prompt_width(struct prompt *, u_int); /* Get prompt flags as a string. */ static const char * @@ -120,6 +141,8 @@ prompt_set_options(struct prompt_create_data *pd, struct session *s) style_apply(&pd->style, oo, "message-style", NULL); style_apply(&pd->command_style, oo, "message-command-style", NULL); + pd->style_str = options_get_string(oo, "message-style"); + pd->command_style_str = options_get_string(oo, "message-command-style"); n = options_get_number(oo, "prompt-cursor-style"); screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); n = options_get_number(oo, "prompt-command-cursor-style"); @@ -179,6 +202,8 @@ prompt_create(const struct prompt_create_data *pd) memcpy(&pr->style, &pd->style, sizeof pr->style); memcpy(&pr->command_style, &pd->command_style, sizeof pr->command_style); + pr->style_str = xstrdup(pd->style_str); + pr->command_style_str = xstrdup(pd->command_style_str); pr->cstyle = pd->cstyle; pr->command_cstyle = pd->command_cstyle; pr->ccolour = pd->ccolour; @@ -201,6 +226,8 @@ prompt_free(struct prompt *pr) if (pr->freecb != NULL && pr->data != NULL) pr->freecb(pr->data); free(pr->message_format); + free(pr->style_str); + free(pr->command_style_str); free(pr->word_separators); free(pr->last); free(pr->string); @@ -321,16 +348,17 @@ prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, * 0 otherwise. */ static int -prompt_redraw_quote(const struct prompt *pr, u_int pcursor, - struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, +prompt_redraw_quote(const struct prompt *pr, u_int pcursor, u_int input_x, + struct screen_write_ctx *ctx, u_int offset, u_int pw, u_int *w, struct grid_cell *gc) { struct utf8_data ud; - if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { + if (pr->flags & PROMPT_QUOTENEXT && + pcursor >= offset && + ctx->s->cx == input_x + pcursor - offset) { utf8_set(&ud, '^'); - return (prompt_redraw_character(ctx, offset, pwidth, - width, gc, &ud)); + return (prompt_redraw_character(ctx, offset, pw, w, gc, &ud)); } return (1); } @@ -368,12 +396,12 @@ prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, free(ud); } -/* Expand prompt string using the current input. */ -static char * -prompt_expand(struct prompt *pr) +/* Create the prompt format tree using the current input. */ +static struct format_tree * +prompt_format_tree(struct prompt *pr) { struct format_tree *ft; - char *expanded, *prompt, *tmp; + char *tmp; if (cmd_find_valid_state(&pr->state)) ft = format_create_from_state(NULL, NULL, &pr->state); @@ -385,31 +413,145 @@ prompt_expand(struct prompt *pr) format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); - prompt = format_expand_time(ft, pr->string); - format_add(ft, "message", "%s", prompt); if (pr->flags & PROMPT_COMMANDMODE) format_add(ft, "command_prompt", "1"); else format_add(ft, "command_prompt", "0"); + return (ft); +} + +/* Expand prompt string using the current input. */ +static char * +prompt_expand1(struct prompt *pr, struct format_tree *ft) +{ + char *expanded, *prompt; + + prompt = format_expand_time(ft, pr->string); + format_add(ft, "message", "%s", prompt); expanded = format_expand_time(ft, pr->message_format); free(prompt); - format_free(ft); return (expanded); } -/* Work out the width used by the prompt string. */ -static u_int -prompt_width(struct prompt *pr, u_int aw) +/* Get the effective message style for the current prompt. */ +static void +prompt_effective_style(struct prompt *pr, struct style *sy, + struct format_tree *ft) { - char *expanded; - u_int start; + const char *s; + struct grid_cell *gc; + char *expanded; - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - free(expanded); - return (start); + if (pr->flags & PROMPT_COMMANDMODE) { + s = pr->command_style_str; + gc = &pr->command_style; + } else { + s = pr->style_str; + gc = &pr->style; + } + + style_set(sy, gc); + if (s != NULL) { + expanded = format_expand_time(ft, s); + if (style_parse(sy, &grid_default_cell, expanded) != 0) + style_set(sy, gc); + free(expanded); + } +} + +/* Work out where the editable prompt content appears. */ +static void +prompt_layout(struct prompt *pr, u_int ax, u_int aw, struct prompt_layout *pl, + char **expanded, struct style *sy) +{ + char *local = NULL; + struct format_tree *ft; + u_int pcursor, pwidth, end, width, offset, avail; + + memset(pl, 0, sizeof *pl); + pl->area_x = ax; + pl->area_width = aw; + + ft = prompt_format_tree(pr); + if (sy != NULL) + prompt_effective_style(pr, sy, ft); + if (expanded != NULL) + *expanded = prompt_expand1(pr, ft); + else { + local = prompt_expand1(pr, ft); + expanded = &local; + } + format_free(ft); + + if (aw == 0) { + free(local); + return; + } + + pl->label_width = format_width(*expanded); + if (pl->label_width > aw) + pl->label_width = aw; + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + + avail = aw - pl->label_width; + if (avail == 0) { + pl->input_offset = 0; + pl->input_width = 0; + pl->cursor_x = pl->label_width; + } else { + if (pcursor >= avail) { + offset = (pcursor - avail) + 1; + width = avail; + } else { + offset = 0; + width = pwidth; + } + if (width > avail) + width = avail; + + pl->input_offset = offset; + pl->input_width = width; + pl->cursor_x = pl->label_width + pcursor - offset; + } + + pl->content_width = pl->label_width + pl->input_width; + if (pr->complete_display != NULL && + pr->index == utf8_strlen(pr->buffer) && + pl->cursor_x < aw) { + avail = aw - pl->cursor_x; + width = utf8_cstrwidth(pr->complete_display); + if (width > avail) + width = avail; + end = pl->cursor_x + width; + if (end > pl->content_width) + pl->content_width = end; + } + if (pl->content_width > aw) + pl->content_width = aw; + + if (sy != NULL) { + switch (sy->align) { + case STYLE_ALIGN_CENTRE: + case STYLE_ALIGN_ABSOLUTE_CENTRE: + pl->content_x = ax + (aw - pl->content_width) / 2; + break; + case STYLE_ALIGN_RIGHT: + pl->content_x = ax + aw - pl->content_width; + break; + default: + pl->content_x = ax; + break; + } + } else + pl->content_x = ax; + + pl->input_x = pl->content_x + pl->label_width; + pl->cursor_x += pl->content_x; + free(local); } /* Choose a completion from a mouse position. */ @@ -460,70 +602,59 @@ prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) { struct screen_write_ctx *ctx = pd->ctx; struct screen *s = ctx->s; - u_int ax = pd->area_x, py = pd->prompt_line; - u_int aw = pd->area_width, *cx = pd->cursor_x; + u_int ax = pd->area_x, py = pd->prompt_line, *cx; + u_int aw = pd->area_width; struct grid_cell gc; - u_int i, offset, left, start, width; - u_int pcursor, pwidth; + struct prompt_layout pl; + struct style sy; + u_int i, width, pcursor; char *expanded; - /* Choose the cursor colour and style for this prompt. */ if (pr->flags & PROMPT_COMMANDMODE) { - memcpy(&gc, &pr->command_style, sizeof gc); s->default_cstyle = pr->command_cstyle; s->default_mode = pr->command_cmode; s->default_ccolour = pr->command_ccolour; } else { - memcpy(&gc, &pr->style, sizeof gc); s->default_cstyle = pr->cstyle; s->default_mode = pr->cmode; s->default_ccolour = pr->ccolour; } - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - *cx = ax + start; + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + memcpy(&gc, &sy.gc, sizeof gc); + cx = pd->cursor_x; + *cx = pl.cursor_x; screen_write_cursormove(ctx, ax, py, 0); - format_draw(ctx, &gc, aw, expanded, NULL, 0); - screen_write_cursormove(ctx, ax + start, py, 0); - free(expanded); - - left = aw - start; - if (left == 0) - return; + if (sy.fill != 8) + screen_write_clearcharacter(ctx, aw, sy.fill); pcursor = utf8_strwidth(pr->buffer, pr->index); - pwidth = utf8_strwidth(pr->buffer, -1); - if (pr->flags & PROMPT_QUOTENEXT) - pwidth++; - if (pcursor >= left) { - /* - * The cursor would be outside the screen so start drawing - * with it on the right. - */ - offset = (pcursor - left) + 1; - pwidth = left; - } else - offset = 0; - if (pwidth > left) - pwidth = left; - *cx = ax + start + pcursor - offset; - width = 0; - for (i = 0; pr->buffer[i].size != 0; i++) { - if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, - &width, &gc)) - break; - if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc, - &pr->buffer[i])) - break; + if (pl.content_width != 0) { + screen_write_cursormove(ctx, pl.content_x, py, 0); + if (pl.label_width != 0) + format_draw(ctx, &gc, pl.label_width, expanded, NULL, + 0); + + screen_write_cursormove(ctx, pl.input_x, py, 0); + width = 0; + for (i = 0; pr->buffer[i].size != 0; i++) { + if (!prompt_redraw_quote(pr, pcursor, pl.input_x, + ctx, pl.input_offset, pl.input_width, &width, + &gc)) + break; + if (!prompt_redraw_character(ctx, pl.input_offset, + pl.input_width, &width, &gc, &pr->buffer[i])) + break; + } + prompt_redraw_quote(pr, pcursor, pl.input_x, ctx, + pl.input_offset, pl.input_width, &width, &gc); + + prompt_draw_complete(pr, ctx, pl.content_x, pl.content_width, + pl.cursor_x, py, &gc); } - prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc); - - prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc); + free(expanded); } /* Move cursor in prompt from a mouse position. */ @@ -532,36 +663,34 @@ prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) { struct utf8_data *ud; enum prompt_key_result result; - u_int cx, start, left, pcursor, pwidth, offset, width; - u_int target; + struct prompt_layout pl; + struct style sy; + char *expanded; + u_int pwidth, width, target; size_t idx; if (x < ax || x >= ax + aw) return (PROMPT_KEY_NOT_HANDLED); - start = prompt_width(pr, aw); - left = aw - start; - if (left == 0) + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + free(expanded); + + if (pl.input_width == 0) return (PROMPT_KEY_HANDLED); - pcursor = utf8_strwidth(pr->buffer, pr->index); pwidth = utf8_strwidth(pr->buffer, -1); if (pr->flags & PROMPT_QUOTENEXT) pwidth++; - if (pcursor >= left) - offset = (pcursor - left) + 1; - else - offset = 0; - cx = ax + start + pcursor - offset; - result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw); + result = prompt_mouse_complete(pr, x, pl.cursor_x, pl.content_x, + pl.content_width, redraw); if (result != PROMPT_KEY_NOT_HANDLED) return (result); - if (x <= ax + start) - target = offset; + if (x <= pl.input_x) + target = pl.input_offset; else - target = offset + x - (ax + start); + target = pl.input_offset + x - pl.input_x; if (target > pwidth) target = pwidth; diff --git a/resize.c b/resize.c index c8257cec0..1d83e4b70 100644 --- a/resize.c +++ b/resize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: resize.c,v 1.56 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: resize.c,v 1.57 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -22,9 +22,27 @@ #include "tmux.h" +static void +resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_window(&fs, w, 0); + event_payload_set_target(ep, &fs); + event_payload_set_window(ep, "window", w); + event_payload_set_uint(ep, "width", w->sx); + event_payload_set_uint(ep, "height", w->sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("window-resized", ep); +} + void resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) { + u_int old_sx = w->sx, old_sy = w->sy; int zoomed; /* Check size limits. */ @@ -61,7 +79,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) tty_update_window_offset(w); server_redraw_window(w); events_fire_window("window-layout-changed", w); - events_fire_window("window-resized", w); + resize_fire_window_resized(w, old_sx, old_sy); w->flags &= ~WINDOW_RESIZE; } diff --git a/server-client.c b/server-client.c index ba5dc9702..b4ac5e6ef 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.486 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.487 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -403,6 +403,62 @@ server_client_attached_lost(struct client *c) } } +/* Fire client session changed. */ +static void +server_client_fire_session_changed(struct client *c, struct session *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) { + event_payload_set_session(ep, "session", fs.s); + event_payload_set_session(ep, "new_session", fs.s); + } + if (old != NULL) + event_payload_set_session(ep, "old_session", old); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + events_fire("client-session-changed", ep); +} + +/* Fire client resized. */ +static void +server_client_fire_resized(struct client *c, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) + event_payload_set_session(ep, "session", fs.s); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + event_payload_set_uint(ep, "width", c->tty.sx); + event_payload_set_uint(ep, "height", c->tty.sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("client-resized", ep); +} + /* Set client session. */ void server_client_set_session(struct client *c, struct session *s) @@ -429,7 +485,7 @@ server_client_set_session(struct client *c, struct session *s) alerts_check_session(s); tty_update_client_offset(c); status_timer_start(c); - events_fire_client("client-session-changed", c); + server_client_fire_session_changed(c, old); server_redraw_client(c); } @@ -466,6 +522,8 @@ server_client_lost(struct client *c) server_client_attached_lost(c); events_fire_client("client-detached", c); } + if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL))) + events_fire_client("client-closed", c); if (c->flags & CLIENT_CONTROL) control_stop(c); @@ -2421,6 +2479,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) struct client *c = arg; ssize_t datalen; struct session *s; + u_int old_sx, old_sy; if (c->flags & CLIENT_DEAD) return; @@ -2459,6 +2518,8 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (c->flags & CLIENT_CONTROL) break; server_client_update_latest(c); + old_sx = c->tty.sx; + old_sy = c->tty.sy; tty_resize(&c->tty); tty_repeat_requests(&c->tty, 0); recalculate_sizes(); @@ -2468,7 +2529,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) c->overlay_resize(c, c->overlay_data); server_redraw_client(c); if (c->session != NULL) - events_fire_client("client-resized", c); + server_client_fire_resized(c, old_sx, old_sy); break; case MSG_EXITING: if (datalen != 0) @@ -2775,6 +2836,8 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) close(c->out_fd); c->out_fd = -1; } + if (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL)) + events_fire_client("client-created", c); /* If pasting has taken too long, turn it off. */ if (c->flags & (CLIENT_BRACKETPASTING|CLIENT_ASSUMEPASTING) && diff --git a/session.c b/session.c index da30f586b..d52b70b88 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.105 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: session.c,v 1.106 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -471,6 +471,29 @@ session_last(struct session *s) return (session_set_current(s, wl)); } +/* Fire session window changed. */ +static void +session_fire_window_changed(struct session *s, struct winlink *wl, + struct winlink *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_winlink(&fs, wl, 0); + event_payload_set_target(ep, &fs); + event_payload_set_session(ep, "session", s); + event_payload_set_window(ep, "window", wl->window); + event_payload_set_window(ep, "new_window", wl->window); + event_payload_set_int(ep, "window_index", wl->idx); + event_payload_set_int(ep, "new_window_index", wl->idx); + if (old != NULL) { + event_payload_set_window(ep, "old_window", old->window); + event_payload_set_int(ep, "old_window_index", old->idx); + } + events_fire("session-window-changed", ep); +} + /* Set current winlink to wl .*/ int session_set_current(struct session *s, struct winlink *wl) @@ -493,7 +516,7 @@ session_set_current(struct session *s, struct winlink *wl) winlink_clear_flags(wl); window_update_activity(wl->window); tty_update_window_offset(wl->window); - events_fire_session("session-window-changed", s); + session_fire_window_changed(s, wl, old); return (0); } @@ -540,12 +563,33 @@ session_group_new(const char *name) return (sg); } +/* Fire session group changed. */ +static void +session_group_fire(const char *name, struct session_group *sg, + struct session *s) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + if (session_alive(s)) { + cmd_find_from_session(&fs, s, 0); + event_payload_set_target(ep, &fs); + } + event_payload_set_session(ep, "session", s); + event_payload_set_string(ep, "group", "%s", sg->name); + event_payload_set_uint(ep, "group_size", session_group_count(sg)); + events_fire(name, ep); +} + /* Add a session to a session group. */ void session_group_add(struct session_group *sg, struct session *s) { - if (session_group_contains(s) == NULL) + if (session_group_contains(s) == NULL) { TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); + session_group_fire("session-added-to-group", sg, s); + } } /* Remove a session from its group and destroy the group if empty. */ @@ -556,6 +600,7 @@ session_group_remove(struct session *s) if ((sg = session_group_contains(s)) == NULL) return; + session_group_fire("session-removed-from-group", sg, s); TAILQ_REMOVE(&sg->sessions, s, gentry); if (TAILQ_EMPTY(&sg->sessions)) { RB_REMOVE(session_groups, &session_groups, sg); diff --git a/spawn.c b/spawn.c index 37798f44f..f11e2e2a0 100644 --- a/spawn.c +++ b/spawn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spawn.c,v 1.47 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: spawn.c,v 1.48 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -229,8 +229,10 @@ spawn_window(struct spawn_context *sc, char **cause) session_select(s, sc->wl->idx); /* Fire notification if new window. */ - if (~sc->flags & SPAWN_RESPAWN) + if (~sc->flags & SPAWN_RESPAWN) { + events_fire_window("window-created", w); events_fire_winlink("window-linked", sc->wl); + } session_group_synchronize_from(s); return (sc->wl); diff --git a/tmux.1 b/tmux.1 index 2ed7c4a14..a4465fca0 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1132 2026/07/10 15:20:06 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1134 2026/07/13 13:01:14 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -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: July 10 2026 $ +.Dd $Mdocdate: July 13 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -4077,6 +4077,21 @@ or full window width (with .Fl v ) , instead of splitting the active pane. .Pp +If +.Ar target\-pane +is floating, a new floating pane is created adjacent to +.Ar target\-pane +with equal sizes. +The position is determined by the +.Fl h , +.Fl v , +and +.Fl b +flags. +If any of the new pane would not be visible on the screen, +.Ar target\-pane +and the new pane are resized to fit. +.Pp .Fl k keeps the pane open after the optional .Ar shell\-command @@ -6343,6 +6358,10 @@ See Run when a client becomes the latest active client of its session. .It client\-attached Run when a client is attached. +.It client\-closed +Run when a client is closed. +.It client\-created +Run when a client is created. .It client\-detached Run when a client is detached. .It client\-focus\-in @@ -6361,6 +6380,8 @@ Run when a client switches to a dark theme. Run when a command fails. .It marked\-pane\-changed Run when the marked pane is set or cleared. +.It pane\-bell +Run when a pane receives a bell. .It pane\-command\-finished Run when an OSC 133 command finishes in a pane. The event payload includes @@ -6394,6 +6415,8 @@ Run when a pane changes mode. Run when a pane enters a mode. .It pane\-mode\-exited Run when a pane exits a mode. +.It pane\-moved +Run when a pane is moved to another window. .It pane\-prompt\-closed Run when a prompt in a pane is closed. .It pane\-prompt\-opened @@ -6412,10 +6435,18 @@ Run when a pane title is changed. Run when a session is closed. .It session\-created Run when a new session is created. +.It session\-added\-to\-group +Run when a session is added to a session group. +.It session\-removed\-from\-group +Run when a session is removed from a session group. .It session\-renamed Run when a session is renamed. .It session\-window\-changed Run when a session changes its active window. +.It window\-closed +Run when a window is closed. +.It window\-created +Run when a window is created. .It window\-layout\-changed Run when a window layout is changed. .It window\-linked diff --git a/tmux.h b/tmux.h index 898cc620b..fdf08f5cf 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1396 2026/07/10 15:45:11 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1399 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2127,6 +2127,8 @@ struct prompt_create_data { struct grid_cell style; struct grid_cell command_style; + const char *style_str; + const char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; @@ -2496,6 +2498,8 @@ struct spawn_context { #define SPAWN_EMPTY 0x40 #define SPAWN_ZOOM 0x80 #define SPAWN_FLOATING 0x100 +#define SPAWN_HORIZONTAL 0x200 +#define SPAWN_SPLIT 0x400 }; /* Paste buffer. */ @@ -3670,6 +3674,8 @@ int window_has_floating_panes(struct window *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); +void window_fire_pane_moved(struct window_pane *, struct window *, + int, struct window *, int); void window_update_focus(struct window *); void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, @@ -3840,10 +3846,13 @@ struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct window_pane *, - char **); + int, char **); int layout_floating_args_parse(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct layout_geometry *, char **); +int layout_split_floating_cell(struct layout_cell *, + struct window *, struct layout_geometry *, enum pane_lines, + int, char **); int layout_remove_tile(struct window *, struct layout_cell *); int layout_insert_tile(struct window *, struct layout_cell *); diff --git a/window-copy.c b/window-copy.c index 997cb8888..6d0390fe9 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-copy.c,v 1.413 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: window-copy.c,v 1.415 2026/07/13 12:53:56 jsg Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -103,8 +103,12 @@ static void window_copy_goto_line(struct window_mode_entry *, const char *); static void window_copy_update_cursor(struct window_mode_entry *, u_int, u_int); static void window_copy_start_selection(struct window_mode_entry *); +static int window_copy_mouse_in_selection(struct window_mode_entry *, + u_int, u_int, int *, int *); static int window_copy_adjust_selection(struct window_mode_entry *, u_int *, u_int *); +static int window_copy_update_selection_view(struct window_mode_entry *, + int, int); static int window_copy_set_selection(struct window_mode_entry *, int, int); static int window_copy_update_selection(struct window_mode_entry *, int, int); @@ -862,7 +866,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 1, 0); + window_copy_update_selection_view(wme, 1, 0); window_pane_scrollbar_show(wp, 1); window_copy_redraw_screen(wme, 1); } @@ -1648,7 +1652,7 @@ window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to) data->cy += delta; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -2361,6 +2365,7 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); return (WINDOW_COPY_CMD_NOTHING); } + for (; np != 0; np--) window_copy_cursor_down(wme, 1); if (data->scroll_exit && data->oy == 0 && data->screen.sel == NULL) @@ -2374,6 +2379,17 @@ window_copy_cmd_scroll_down_and_cancel(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move down in the history. */ + window_copy_scroll_up(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } for (; np != 0; np--) window_copy_cursor_down(wme, 1); @@ -2388,6 +2404,7 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; /* * If at the top, nothing can change, so return WINDOW_COPY_CMD_NOTHING @@ -2395,6 +2412,17 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) */ if (data->oy == screen_hsize(data->backing)) return (WINDOW_COPY_CMD_NOTHING); + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move up in the history. */ + window_copy_scroll_down(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } + for (; np != 0; np--) window_copy_cursor_up(wme, 1); return (WINDOW_COPY_CMD_MOVE); @@ -3117,7 +3145,7 @@ window_copy_cmd_recentre_top_bottom(struct window_copy_cmd_state *cs) data->cy = cy + (data->oy - oy); break; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -5504,6 +5532,76 @@ window_copy_start_selection(struct window_mode_entry *wme) window_copy_set_selection(wme, 1, 0); } +/* + * Return whether x,y is inside the selection and optionally identify the + * endpoint. + */ +static int +window_copy_mouse_in_selection(struct window_mode_entry *wme, u_int x, u_int y, + int *on_start, int *on_end) +{ + struct window_copy_mode_data *data = wme->data; + u_int sx = screen_size_x(&data->screen); + u_int sy = screen_size_y(&data->screen); + u_int hsize, screeny; + u_int mx, my, selx, sely, endselx, endsely; + u_int cursorx, cursory; + long long mpos, spos, epos, dstart, dend; + + if (on_start != NULL) + *on_start = 0; + if (on_end != NULL) + *on_end = 0; + if (data->screen.sel == NULL) + return (0); + + hsize = screen_hsize(data->backing); + screeny = hsize - data->oy; + + selx = window_copy_cursor_offset(wme, data->selx, sx); + sely = data->sely - screeny; + if (data->sely >= screeny && sely < sy && x == selx && y == sely) { + if (on_start != NULL) + *on_start = 1; + return (1); + } + + endselx = window_copy_cursor_offset(wme, data->endselx, sx); + endsely = data->endsely - screeny; + if (data->endsely >= screeny && + endsely < sy && + x == endselx && y == endsely) { + if (on_end != NULL) + *on_end = 1; + return (1); + } + + cursorx = window_copy_cursor_offset(wme, data->cx, sx); + cursory = data->cy; + if (x != cursorx || y != cursory) { + if (!screen_check_selection(&data->screen, x, y)) + return (0); + } + + if (on_start != NULL || on_end != NULL) { + mx = window_copy_cursor_unoffset(wme, x, sx); + my = screeny + y; + mpos = (long long)my * (sx + 1) + mx; + spos = (long long)data->sely * (sx + 1) + data->selx; + epos = (long long)data->endsely * (sx + 1) + data->endselx; + dstart = llabs(mpos - spos); + dend = llabs(mpos - epos); + if (dstart <= dend) { + if (on_start != NULL) + *on_start = 1; + } else { + if (on_end != NULL) + *on_end = 1; + } + } + return (1); +} + static int window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx, u_int *sely) @@ -5549,6 +5647,35 @@ window_copy_update_selection(struct window_mode_entry *wme, int may_redraw, return (window_copy_set_selection(wme, may_redraw, no_reset)); } +/* + * Update the visible selection after the view has changed. If the mouse is + * not dragging, keep the selection endpoints anchored to the same text. + */ +static int +window_copy_update_selection_view(struct window_mode_entry *wme, int may_redraw, + int no_reset) +{ + struct window_copy_mode_data *data = wme->data; + u_int selx, sely, endselx, endsely; + int changed; + + if (data->cursordrag != CURSORDRAG_NONE) + return (window_copy_update_selection(wme, may_redraw, no_reset)); + + selx = data->selx; + sely = data->sely; + endselx = data->endselx; + endsely = data->endsely; + + changed = window_copy_update_selection(wme, may_redraw, 1); + + data->selx = selx; + data->sely = sely; + data->endselx = endselx; + data->endsely = endsely; + return (changed); +} + static int window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, int no_reset) @@ -6578,7 +6705,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { @@ -6649,7 +6776,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { @@ -6738,6 +6865,7 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) struct window_mode_entry *wme; struct window_copy_mode_data *data; u_int x, y, yg; + int inside_selection, on_start, on_end; if (c == NULL) return; @@ -6758,10 +6886,16 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) c->tty.mouse_drag_release = window_copy_drag_release; data = wme->data; + on_start = on_end = 0; + inside_selection = window_copy_mouse_in_selection(wme, x, y, + &on_start, &on_end); x = window_copy_cursor_unoffset(wme, x, screen_size_x(&data->screen)); yg = screen_hsize(data->backing) + y - data->oy; - if (x < data->selrx || x > data->endselrx || yg != data->selry) + if (on_start || on_end || !inside_selection || + x < data->selrx || x > data->endselrx || yg != data->selry) { + data->lineflag = LINE_SEL_NONE; data->selflag = SEL_CHAR; + } switch (data->selflag) { case SEL_WORD: if (data->separators != NULL) { @@ -6777,7 +6911,15 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) break; case SEL_CHAR: window_copy_update_cursor(wme, x, y); - window_copy_start_selection(wme); + if (!inside_selection) + window_copy_start_selection(wme); + else { + if (on_start) + data->cursordrag = CURSORDRAG_SEL; + else if (on_end) + data->cursordrag = CURSORDRAG_ENDSEL; + window_copy_update_selection(wme, 1, 0); + } break; } @@ -6853,6 +6995,7 @@ window_copy_drag_release(struct client *c, struct mouse_event *m) data = wme->data; if (window_copy_line_numbers_active(wme)) window_copy_drag_update(c, m); + data->cursordrag = CURSORDRAG_NONE; evtimer_del(&data->dragtimer); } diff --git a/window.c b/window.c index 8b26c7d20..70cd9a559 100644 --- a/window.c +++ b/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.360 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.361 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -128,6 +128,29 @@ window_fire_pane_changed(struct window *w, struct window_pane *wp, events_fire("window-pane-changed", ep); } +void +window_fire_pane_moved(struct window_pane *wp, struct window *old_w, + int old_idx, struct window *new_w, int new_idx) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", new_w); + event_payload_set_window(ep, "old_window", old_w); + event_payload_set_window(ep, "new_window", new_w); + if (old_idx != -1) + event_payload_set_int(ep, "old_window_index", old_idx); + if (new_idx != -1) { + event_payload_set_int(ep, "window_index", new_idx); + event_payload_set_int(ep, "new_window_index", new_idx); + } + events_fire("pane-moved", ep); +} + static void window_fire_pane_mode_changed(const char *name, struct window_pane *wp, const char *previous, const char *current, int entered) @@ -488,6 +511,9 @@ window_add_ref(struct window *w, const char *from) void window_remove_ref(struct window *w, const char *from) { + if (w->references == 1) + events_fire_window("window-closed", w); + w->references--; log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references); @@ -979,7 +1005,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (w->active != NULL) { window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; - events_fire_window("window-pane-changed", w); + window_fire_pane_changed(w, w->active, wp); window_update_focus(w); } }