diff --git a/client.c b/client.c index 26e48df3..a556a86f 100644 --- a/client.c +++ b/client.c @@ -266,8 +266,13 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER)) flags |= CLIENT_STARTSERVER; cmd_list_free(pr->cmdlist); - } else + } else { + fprintf(stderr, "%s\n", pr->error); + args_free_values(values, argc); + free(values); free(pr->error); + return 1; + } args_free_values(values, argc); free(values); } diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 9c4b1508..a5582e46 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -99,7 +99,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) w = wp->window = window_create(w->sx, w->sy, w->xpixel, w->ypixel); options_set_parent(wp->options, w->options); - wp->flags |= PANE_STYLECHANGED; + wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); TAILQ_INSERT_HEAD(&w->panes, wp, entry); w->active = wp; w->latest = tc; diff --git a/cmd-join-pane.c b/cmd-join-pane.c index da1ba9ae..6b6421fa 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -149,7 +149,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); - src_wp->flags |= PANE_STYLECHANGED; + src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); if (flags & SPAWN_BEFORE) TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); else diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 135729f5..3cabe07e 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -149,12 +149,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) markedwp = marked_pane.wp; if (lastwp != NULL) { - lastwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + lastwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED| + PANE_THEMECHANGED); server_redraw_window_borders(lastwp->window); server_status_window(lastwp->window); } if (markedwp != NULL) { - markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED| + PANE_THEMECHANGED); server_redraw_window_borders(markedwp->window); server_status_window(markedwp->window); } @@ -169,7 +171,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } options_set_string(oo, "window-active-style", 0, "%s", style); - wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED); } if (args_has(args, 'g')) { cmdq_print(item, "%s", options_get_string(oo, "window-style")); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index c270fbd1..35b3f140 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -217,7 +217,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'R')) { colour_palette_clear(&wp->palette); input_reset(wp->ictx, 1); - wp->flags |= (PANE_STYLECHANGED|PANE_REDRAW); + wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED|PANE_REDRAW); } if (count == 0) { diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 6931bd16..4680f598 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -101,10 +101,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); - src_wp->flags |= PANE_STYLECHANGED; + src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); dst_wp->window = src_w; options_set_parent(dst_wp->options, src_w->options); - dst_wp->flags |= PANE_STYLECHANGED; + dst_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); sx = src_wp->sx; sy = src_wp->sy; xoff = src_wp->xoff; yoff = src_wp->yoff; diff --git a/colour.c b/colour.c index 5fd91c9c..903090d3 100644 --- a/colour.c +++ b/colour.c @@ -182,6 +182,46 @@ colour_tostring(int c) return ("invalid"); } +/* Convert background colour to theme. */ +enum client_theme +colour_totheme(int c) +{ + int r, g, b, brightness; + + if (c == -1) + return (THEME_UNKNOWN); + + if (c & COLOUR_FLAG_RGB) { + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = (c >> 0) & 0xff; + + brightness = r + g + b; + if (brightness > 382) + return (THEME_LIGHT); + return (THEME_DARK); + } + + if (c & COLOUR_FLAG_256) + return (colour_totheme(colour_256toRGB(c))); + + switch (c) { + case 0: + case 90: + return (THEME_DARK); + case 7: + case 97: + return (THEME_LIGHT); + default: + if (c >= 0 && c <= 7) + return (colour_totheme(colour_256toRGB(c))); + if (c >= 90 && c <= 97) + return (colour_totheme(colour_256toRGB(8 + c - 90))); + break; + } + return (THEME_UNKNOWN); +} + /* Convert colour from string. */ int colour_fromstring(const char *s) diff --git a/format.c b/format.c index 221550a1..9ce56104 100644 --- a/format.c +++ b/format.c @@ -1544,6 +1544,23 @@ format_cb_client_written(struct format_tree *ft) return (NULL); } +/* Callback for client_theme. */ +static void * +format_cb_client_theme(struct format_tree *ft) +{ + if (ft->c != NULL) { + switch (ft->c->theme) { + case THEME_DARK: + return (xstrdup("dark")); + case THEME_LIGHT: + return (xstrdup("light")); + case THEME_UNKNOWN: + return (NULL); + } + } + return (NULL); +} + /* Callback for config_files. */ static void * format_cb_config_files(__unused struct format_tree *ft) @@ -2881,6 +2898,9 @@ static const struct format_table_entry format_table[] = { { "client_termtype", FORMAT_TABLE_STRING, format_cb_client_termtype }, + { "client_theme", FORMAT_TABLE_STRING, + format_cb_client_theme + }, { "client_tty", FORMAT_TABLE_STRING, format_cb_client_tty }, diff --git a/input-keys.c b/input-keys.c index 097feeb2..18f15afa 100644 --- a/input-keys.c +++ b/input-keys.c @@ -53,9 +53,15 @@ static struct input_key_entry input_key_defaults[] = { { .key = KEYC_PASTE_START, .data = "\033[200~" }, + { .key = KEYC_PASTE_START|KEYC_IMPLIED_META, + .data = "\033[200~" + }, { .key = KEYC_PASTE_END, .data = "\033[201~" }, + { .key = KEYC_PASTE_END|KEYC_IMPLIED_META, + .data = "\033[201~" + }, /* Function keys. */ { .key = KEYC_F1, @@ -307,6 +313,12 @@ static struct input_key_entry input_key_defaults[] = { { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, .data = "\033[3;_~" }, + { .key = KEYC_REPORT_DARK_THEME, + .data = "\033[?997;1n" + }, + { .key = KEYC_REPORT_LIGHT_THEME, + .data = "\033[?997;2n" + }, }; static const key_code input_key_modifiers[] = { 0, diff --git a/input.c b/input.c index 6216cfb8..2c7b77b8 100644 --- a/input.c +++ b/input.c @@ -133,7 +133,7 @@ static void printflike(2, 3) input_reply(struct input_ctx *, const char *, ...); static void input_set_state(struct input_ctx *, const struct input_transition *); static void input_reset_cell(struct input_ctx *); - +static void input_report_current_theme(struct input_ctx *); static void input_osc_4(struct input_ctx *, const char *); static void input_osc_8(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *); @@ -243,6 +243,7 @@ enum input_csi_type { INPUT_CSI_DECSTBM, INPUT_CSI_DL, INPUT_CSI_DSR, + INPUT_CSI_DSR_PRIVATE, INPUT_CSI_ECH, INPUT_CSI_ED, INPUT_CSI_EL, @@ -251,6 +252,7 @@ enum input_csi_type { INPUT_CSI_IL, INPUT_CSI_MODOFF, INPUT_CSI_MODSET, + INPUT_CSI_QUERY_PRIVATE, INPUT_CSI_RCP, INPUT_CSI_REP, INPUT_CSI_RM, @@ -259,8 +261,8 @@ enum input_csi_type { INPUT_CSI_SD, INPUT_CSI_SGR, INPUT_CSI_SM, - INPUT_CSI_SM_PRIVATE, INPUT_CSI_SM_GRAPHICS, + INPUT_CSI_SM_PRIVATE, INPUT_CSI_SU, INPUT_CSI_TBC, INPUT_CSI_VPA, @@ -304,6 +306,8 @@ static const struct input_table_entry input_csi_table[] = { { 'm', ">", INPUT_CSI_MODSET }, { 'n', "", INPUT_CSI_DSR }, { 'n', ">", INPUT_CSI_MODOFF }, + { 'n', "?", INPUT_CSI_DSR_PRIVATE }, + { 'p', "?$", INPUT_CSI_QUERY_PRIVATE }, { 'q', " ", INPUT_CSI_DECSCUSR }, { 'q', ">", INPUT_CSI_XDA }, { 'r', "", INPUT_CSI_DECSTBM }, @@ -1531,6 +1535,20 @@ input_csi_dispatch(struct input_ctx *ictx) if (n != -1) screen_write_deleteline(sctx, n, bg); break; + case INPUT_CSI_DSR_PRIVATE: + switch (input_get(ictx, 0, 0, 0)) { + case 996: + input_report_current_theme(ictx); + break; + } + break; + case INPUT_CSI_QUERY_PRIVATE: + switch (input_get(ictx, 0, 0, 0)) { + case 2031: + input_reply(ictx, "\033[?2031;2$y"); + break; + } + break; case INPUT_CSI_DSR: switch (input_get(ictx, 0, 0, 0)) { case -1: @@ -1781,6 +1799,9 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) case 2004: screen_write_mode_clear(sctx, MODE_BRACKETPASTE); break; + case 2031: + screen_write_mode_clear(sctx, MODE_THEME_UPDATES); + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -1876,6 +1897,9 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) case 2004: screen_write_mode_set(sctx, MODE_BRACKETPASTE); break; + case 2031: + screen_write_mode_set(sctx, MODE_THEME_UPDATES); + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -2697,84 +2721,6 @@ bad: free(id); } -/* - * Get a client with a foreground for the pane. There isn't much to choose - * between them so just use the first. - */ -static int -input_get_fg_client(struct window_pane *wp) -{ - struct window *w = wp->window; - struct client *loop; - - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->flags & CLIENT_UNATTACHEDFLAGS) - continue; - if (loop->session == NULL || !session_has(loop->session, w)) - continue; - if (loop->tty.fg == -1) - continue; - return (loop->tty.fg); - } - return (-1); -} - -/* Get a client with a background for the pane. */ -static int -input_get_bg_client(struct window_pane *wp) -{ - struct window *w = wp->window; - struct client *loop; - - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->flags & CLIENT_UNATTACHEDFLAGS) - continue; - if (loop->session == NULL || !session_has(loop->session, w)) - continue; - if (loop->tty.bg == -1) - continue; - return (loop->tty.bg); - } - return (-1); -} - -/* - * If any control mode client exists that has provided a bg color, return it. - * Otherwise, return -1. - */ -static int -input_get_bg_control_client(struct window_pane *wp) -{ - struct client *c; - - if (wp->control_bg == -1) - return (-1); - - TAILQ_FOREACH(c, &clients, entry) { - if (c->flags & CLIENT_CONTROL) - return (wp->control_bg); - } - return (-1); -} - -/* - * If any control mode client exists that has provided a fg color, return it. - * Otherwise, return -1. - */ -static int -input_get_fg_control_client(struct window_pane *wp) -{ - struct client *c; - - if (wp->control_fg == -1) - return (-1); - - TAILQ_FOREACH(c, &clients, entry) { - if (c->flags & CLIENT_CONTROL) - return (wp->control_fg); - } - return (-1); -} /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void @@ -2787,11 +2733,11 @@ input_osc_10(struct input_ctx *ictx, const char *p) if (strcmp(p, "?") == 0) { if (wp == NULL) return; - c = input_get_fg_control_client(wp); + c = window_pane_get_fg_control_client(wp); if (c == -1) { tty_default_colours(&defaults, wp); if (COLOUR_DEFAULT(defaults.fg)) - c = input_get_fg_client(wp); + c = window_pane_get_fg(wp); else c = defaults.fg; } @@ -2832,20 +2778,12 @@ static void input_osc_11(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; - struct grid_cell defaults; int c; if (strcmp(p, "?") == 0) { if (wp == NULL) return; - c = input_get_bg_control_client(wp); - if (c == -1) { - tty_default_colours(&defaults, wp); - if (COLOUR_DEFAULT(defaults.bg)) - c = input_get_bg_client(wp); - else - c = defaults.bg; - } + c = window_pane_get_bg(wp); input_osc_colour_reply(ictx, 11, c); return; } @@ -2857,7 +2795,7 @@ input_osc_11(struct input_ctx *ictx, const char *p) if (ictx->palette != NULL) { ictx->palette->bg = c; if (wp != NULL) - wp->flags |= PANE_STYLECHANGED; + wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); screen_write_fullredraw(&ictx->ctx); } } @@ -2873,7 +2811,7 @@ input_osc_111(struct input_ctx *ictx, const char *p) if (ictx->palette != NULL) { ictx->palette->bg = 8; if (wp != NULL) - wp->flags |= PANE_STYLECHANGED; + wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); screen_write_fullredraw(&ictx->ctx); } } @@ -3064,3 +3002,18 @@ input_set_buffer_size(size_t buffer_size) log_debug("%s: %lu -> %lu", __func__, input_buffer_size, buffer_size); input_buffer_size = buffer_size; } + +static void +input_report_current_theme(struct input_ctx *ictx) +{ + switch (window_pane_get_theme(ictx->wp)) { + case THEME_DARK: + input_reply(ictx, "\033[?997;1n"); + break; + case THEME_LIGHT: + input_reply(ictx, "\033[?997;2n"); + break; + case THEME_UNKNOWN: + break; + } +} diff --git a/options-table.c b/options-table.c index 39215350..a461b3ea 100644 --- a/options-table.c +++ b/options-table.c @@ -1454,6 +1454,8 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("client-focus-out", ""), OPTIONS_TABLE_HOOK("client-resized", ""), OPTIONS_TABLE_HOOK("client-session-changed", ""), + OPTIONS_TABLE_HOOK("client-light-theme", ""), + OPTIONS_TABLE_HOOK("client-dark-theme", ""), OPTIONS_TABLE_HOOK("command-error", ""), OPTIONS_TABLE_PANE_HOOK("pane-died", ""), OPTIONS_TABLE_PANE_HOOK("pane-exited", ""), diff --git a/options.c b/options.c index 4beb9898..5541a376 100644 --- a/options.c +++ b/options.c @@ -1165,7 +1165,7 @@ options_push_changes(const char *name) if (strcmp(name, "window-style") == 0 || strcmp(name, "window-active-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) - wp->flags |= PANE_STYLECHANGED; + wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); } if (strcmp(name, "pane-colours") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) diff --git a/screen-redraw.c b/screen-redraw.c index 34fd0069..0d2acad6 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -433,11 +433,15 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, const char *fmt; struct format_tree *ft; char *expanded; - int pane_status = rctx->pane_status; + int pane_status = rctx->pane_status, sb_w = 0; + int pane_scrollbars = rctx->pane_scrollbars; u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; + if (window_pane_show_scrollbar(wp, pane_scrollbars)) + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); format_defaults(ft, c, c->session, c->session->curw, wp); @@ -451,7 +455,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, if (wp->sx < 4) wp->status_size = width = 0; else - wp->status_size = width = wp->sx - 4; + wp->status_size = width = wp->sx + sb_w - 2; memcpy(&old, &wp->status_screen, sizeof old); screen_init(&wp->status_screen, width, 1, 0); diff --git a/server-client.c b/server-client.c index 168779e0..ffeb690b 100644 --- a/server-client.c +++ b/server-client.c @@ -56,11 +56,11 @@ static void server_client_set_title(struct client *); static void server_client_set_path(struct client *); static void server_client_reset_state(struct client *); static void server_client_update_latest(struct client *); - static void server_client_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); static void server_client_dispatch_identify(struct client *, struct imsg *); static void server_client_dispatch_shell(struct client *); +static void server_client_report_theme(struct client *, enum client_theme); /* Compare client windows. */ static int @@ -300,6 +300,7 @@ server_client_create(int fd) c->tty.sx = 80; c->tty.sy = 24; + c->theme = THEME_UNKNOWN; status_init(c); c->flags |= CLIENT_FOCUSED; @@ -401,6 +402,7 @@ server_client_set_session(struct client *c, struct session *s) recalculate_sizes(); window_update_focus(s->curw->window); session_update_activity(s, NULL); + session_theme_changed(s); gettimeofday(&s->last_attached_time, NULL); s->curw->flags &= ~WINLINK_ALERTFLAGS; s->curw->window->latest = c; @@ -2243,13 +2245,13 @@ out: static int server_client_is_bracket_paste(struct client *c, key_code key) { - if (key == KEYC_PASTE_START) { + if ((key & KEYC_MASK_KEY) == KEYC_PASTE_START) { c->flags |= CLIENT_BRACKETPASTING; log_debug("%s: bracket paste on", c->name); return (0); } - if (key == KEYC_PASTE_END) { + if ((key & KEYC_MASK_KEY) == KEYC_PASTE_END) { c->flags &= ~CLIENT_BRACKETPASTING; log_debug("%s: bracket paste off", c->name); return (0); @@ -2384,6 +2386,16 @@ server_client_key_callback(struct cmdq_item *item, void *data) event->key = key; } + /* Handle theme reporting keys. */ + if (key == KEYC_REPORT_LIGHT_THEME) { + server_client_report_theme(c, THEME_LIGHT); + goto out; + } + if (key == KEYC_REPORT_DARK_THEME) { + server_client_report_theme(c, THEME_DARK); + goto out; + } + /* Find affected pane. */ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) cmd_find_from_client(&fs, c, 0); @@ -2676,6 +2688,12 @@ server_client_loop(void) } check_window_name(w); } + + /* Send theme updates. */ + RB_FOREACH(w, windows, &windows) { + TAILQ_FOREACH(wp, &w->panes, entry) + window_pane_send_theme_update(wp); + } } /* Check if window needs to be resized. */ @@ -3912,3 +3930,21 @@ out: if (!parse) free(msg); } + +static void +server_client_report_theme(struct client *c, enum client_theme theme) +{ + if (theme == THEME_LIGHT) { + c->theme = THEME_LIGHT; + notify_client("client-light-theme", c); + } else { + c->theme = THEME_DARK; + notify_client("client-dark-theme", c); + } + + /* + * Request foreground and background colour again. Don't forward 2031 to + * panes until a response is received. + */ + tty_puts(&c->tty, "\033]10;?\033\\\033]11;?\033\\"); +} diff --git a/session.c b/session.c index e4aca34d..e6a26d27 100644 --- a/session.c +++ b/session.c @@ -751,3 +751,16 @@ session_renumber_windows(struct session *s) RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1) winlink_remove(&old_wins, wl); } + +/* Set the PANE_THEMECHANGED flag for every pane in this session. */ +void +session_theme_changed(struct session *s) +{ + struct window_pane *wp; + struct winlink *wl; + + RB_FOREACH(wl, winlinks, &s->windows) { + TAILQ_FOREACH(wp, &wl->window->panes, entry) + wp->flags |= PANE_THEMECHANGED; + } +} diff --git a/tmux.1 b/tmux.1 index c92c5650..483e3987 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5869,6 +5869,12 @@ with .Ql bar/ throughout. .Pp +Multiple modifiers may be separated with a semicolon (;) as in +.Ql #{T;=10:status-left} , +which limits the resulting +.Xr strftime 3 -expanded +string to at most 10 characters. +.Pp In addition, the last line of a shell command's output may be inserted using .Ql #() . For example, diff --git a/tmux.h b/tmux.h index 56ac0b55..73c4ba44 100644 --- a/tmux.h +++ b/tmux.h @@ -176,7 +176,8 @@ struct winlink; /* Is this a paste key? */ #define KEYC_IS_PASTE(key) \ - ((key) == KEYC_PASTE_START || (key) == KEYC_PASTE_END) + (((key) & KEYC_MASK_KEY) == KEYC_PASTE_START || \ + ((key) & KEYC_MASK_KEY) == KEYC_PASTE_END) /* Multiple click timeout. */ #define KEYC_CLICK_TIMEOUT 300 @@ -377,6 +378,10 @@ enum { KEYC_KP_ZERO, KEYC_KP_PERIOD, + /* Theme reporting. */ + KEYC_REPORT_DARK_THEME, + KEYC_REPORT_LIGHT_THEME, + /* End of special keys. */ KEYC_BASE_END }; @@ -644,6 +649,7 @@ enum tty_code_code { #define MODE_CURSOR_VERY_VISIBLE 0x10000 #define MODE_CURSOR_BLINKING_SET 0x20000 #define MODE_KEYS_EXTENDED_2 0x40000 +#define MODE_THEME_UPDATES 0x80000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) @@ -1155,8 +1161,9 @@ struct window_pane { #define PANE_STATUSDRAWN 0x400 #define PANE_EMPTY 0x800 #define PANE_STYLECHANGED 0x1000 -#define PANE_UNSEENCHANGES 0x2000 -#define PANE_REDRAWSCROLLBAR 0x4000 +#define PANE_THEMECHANGED 0x2000 +#define PANE_UNSEENCHANGES 0x4000 +#define PANE_REDRAWSCROLLBAR 0x8000 u_int sb_slider_y; u_int sb_slider_h; @@ -1864,6 +1871,16 @@ struct overlay_ranges { u_int nx[OVERLAY_MAX_RANGES]; }; +/* + * Client theme, this is worked out from the background colour if not reported + * by terminal. + */ +enum client_theme { + THEME_UNKNOWN, + THEME_LIGHT, + THEME_DARK +}; + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); @@ -1923,6 +1940,7 @@ struct client { struct mouse_event click_event; struct status_line status; + enum client_theme theme; #define CLIENT_TERMINAL 0x1 #define CLIENT_LOGIN 0x2 @@ -2930,6 +2948,8 @@ void input_parse_screen(struct input_ctx *, struct screen *, void input_reply_clipboard(struct bufferevent *, const char *, size_t, const char *); void input_set_buffer_size(size_t); +int input_get_bg_client(struct window_pane *); +int input_get_bg_control_client(struct window_pane *); /* input-key.c */ void input_key_build(void); @@ -2944,7 +2964,8 @@ int colour_join_rgb(u_char, u_char, u_char); void colour_split_rgb(int, u_char *, u_char *, u_char *); int colour_force_rgb(int); const char *colour_tostring(int); -int colour_fromstring(const char *s); +enum client_theme colour_totheme(int); +int colour_fromstring(const char *); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); @@ -3245,6 +3266,13 @@ void window_set_fill_character(struct window *); void window_pane_default_cursor(struct window_pane *); int window_pane_mode(struct window_pane *); int window_pane_show_scrollbar(struct window_pane *, int); +int window_pane_get_bg(struct window_pane *); +int window_pane_get_fg(struct window_pane *); +int window_pane_get_fg_control_client(struct window_pane *); +int window_pane_get_bg_control_client(struct window_pane *); +int window_get_bg_client(struct window_pane *); +enum client_theme window_pane_get_theme(struct window_pane *); +void window_pane_send_theme_update(struct window_pane *); /* layout.c */ u_int layout_count_cells(struct layout_cell *); @@ -3442,6 +3470,7 @@ void session_group_synchronize_from(struct session *); u_int session_group_count(struct session_group *); u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); +void session_theme_changed(struct session *); /* utf8.c */ enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *); diff --git a/tty-keys.c b/tty-keys.c index 0de31c5d..45175171 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -208,11 +208,15 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[O", KEYC_FOCUS_OUT }, /* Paste keys. */ - { "\033[200~", KEYC_PASTE_START }, - { "\033[201~", KEYC_PASTE_END }, + { "\033[200~", KEYC_PASTE_START|KEYC_IMPLIED_META }, + { "\033[201~", KEYC_PASTE_END|KEYC_IMPLIED_META }, /* Extended keys. */ { "\033[1;5Z", '\011'|KEYC_CTRL|KEYC_SHIFT }, + + /* Theme reporting. */ + { "\033[?997;1n", KEYC_REPORT_DARK_THEME }, + { "\033[?997;2n", KEYC_REPORT_LIGHT_THEME }, }; /* Default xterm keys. */ @@ -791,10 +795,12 @@ tty_keys_next(struct tty *tty) switch (tty_keys_colours(tty, buf, len, &size, &tty->fg, &tty->bg)) { case 0: /* yes */ key = KEYC_UNKNOWN; + session_theme_changed(tty->client->session); goto complete_key; case -1: /* no, or not valid */ break; case 1: /* partial */ + session_theme_changed(tty->client->session); goto partial_key; } diff --git a/tty.c b/tty.c index ecca6cd9..ea2fa941 100644 --- a/tty.c +++ b/tty.c @@ -356,6 +356,11 @@ tty_start_tty(struct tty *tty) if (tty_term_has(tty->term, TTYC_ENBP)) tty_putcode(tty, TTYC_ENBP); + if (tty->term->flags & TERM_VT100LIKE) { + /* Subscribe to theme changes and request theme now. */ + tty_puts(tty, "\033[?2031h\033[?996n"); + } + evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -468,6 +473,9 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG)); tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP)); + if (tty->term->flags & TERM_VT100LIKE) + tty_raw(tty, "\033[?2031l"); + setblocking(c->fd, 1); } diff --git a/window.c b/window.c index 11a00172..97d666fe 100644 --- a/window.c +++ b/window.c @@ -950,7 +950,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp = xcalloc(1, sizeof *wp); wp->window = w; wp->options = options_create(w->options); - wp->flags = PANE_STYLECHANGED; + wp->flags = (PANE_STYLECHANGED|PANE_THEMECHANGED); wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); @@ -1359,6 +1359,36 @@ window_pane_choose_best(struct window_pane **list, u_int size) return (best); } +/* + * Get full size and offset of a window pane including the area of the + * scrollbars if they were visible but not including the border(s). + */ +static void +window_pane_full_size_offset(struct window_pane *wp, u_int *xoff, u_int *yoff, + u_int *sx, u_int *sy) +{ + struct window *w = wp->window; + int pane_scrollbars; + u_int sb_w, sb_pos; + + pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); + sb_pos = options_get_number(w->options, "pane-scrollbars-position"); + + if (window_pane_show_scrollbar(wp, pane_scrollbars)) + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + else + sb_w = 0; + if (sb_pos == PANE_SCROLLBARS_LEFT) { + *xoff = wp->xoff - sb_w; + *sx = wp->sx + sb_w; + } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ + *xoff = wp->xoff; + *sx = wp->sx + sb_w; + } + *yoff = wp->yoff; + *sy = wp->sy; +} + /* * Find the pane directly above another. We build a list of those adjacent to * top edge and then choose the best. @@ -1370,6 +1400,7 @@ window_pane_find_up(struct window_pane *wp) struct window_pane *next, *best, **list; u_int edge, left, right, end, size; int status, found; + u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); @@ -1379,7 +1410,9 @@ window_pane_find_up(struct window_pane *wp) list = NULL; size = 0; - edge = wp->yoff; + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + + edge = yoff; if (status == PANE_STATUS_TOP) { if (edge == 1) edge = w->sy + 1; @@ -1391,20 +1424,21 @@ window_pane_find_up(struct window_pane *wp) edge = w->sy + 1; } - left = wp->xoff; - right = wp->xoff + wp->sx; + left = xoff; + right = xoff + sx; TAILQ_FOREACH(next, &w->panes, entry) { + window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (next->yoff + next->sy + 1 != edge) + if (yoff + sy + 1 != edge) continue; - end = next->xoff + next->sx - 1; + end = xoff + sx - 1; found = 0; - if (next->xoff < left && end > right) + if (xoff < left && end > right) found = 1; - else if (next->xoff >= left && next->xoff <= right) + else if (xoff >= left && xoff <= right) found = 1; else if (end >= left && end <= right) found = 1; @@ -1427,6 +1461,7 @@ window_pane_find_down(struct window_pane *wp) struct window_pane *next, *best, **list; u_int edge, left, right, end, size; int status, found; + u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); @@ -1436,7 +1471,9 @@ window_pane_find_down(struct window_pane *wp) list = NULL; size = 0; - edge = wp->yoff + wp->sy + 1; + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + + edge = yoff + sy + 1; if (status == PANE_STATUS_TOP) { if (edge >= w->sy) edge = 1; @@ -1452,16 +1489,17 @@ window_pane_find_down(struct window_pane *wp) right = wp->xoff + wp->sx; TAILQ_FOREACH(next, &w->panes, entry) { + window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (next->yoff != edge) + if (yoff != edge) continue; - end = next->xoff + next->sx - 1; + end = xoff + sx - 1; found = 0; - if (next->xoff < left && end > right) + if (xoff < left && end > right) found = 1; - else if (next->xoff >= left && next->xoff <= right) + else if (xoff >= left && xoff <= right) found = 1; else if (end >= left && end <= right) found = 1; @@ -1484,6 +1522,7 @@ window_pane_find_left(struct window_pane *wp) struct window_pane *next, *best, **list; u_int edge, top, bottom, end, size; int found; + u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); @@ -1492,24 +1531,27 @@ window_pane_find_left(struct window_pane *wp) list = NULL; size = 0; - edge = wp->xoff; + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + + edge = xoff; if (edge == 0) edge = w->sx + 1; - top = wp->yoff; - bottom = wp->yoff + wp->sy; + top = yoff; + bottom = yoff + sy; TAILQ_FOREACH(next, &w->panes, entry) { + window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (next->xoff + next->sx + 1 != edge) + if (xoff + sx + 1 != edge) continue; - end = next->yoff + next->sy - 1; + end = yoff + sy - 1; found = 0; - if (next->yoff < top && end > bottom) + if (yoff < top && end > bottom) found = 1; - else if (next->yoff >= top && next->yoff <= bottom) + else if (yoff >= top && yoff <= bottom) found = 1; else if (end >= top && end <= bottom) found = 1; @@ -1532,6 +1574,7 @@ window_pane_find_right(struct window_pane *wp) struct window_pane *next, *best, **list; u_int edge, top, bottom, end, size; int found; + u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); @@ -1540,7 +1583,9 @@ window_pane_find_right(struct window_pane *wp) list = NULL; size = 0; - edge = wp->xoff + wp->sx + 1; + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + + edge = xoff + sx + 1; if (edge >= w->sx) edge = 0; @@ -1548,16 +1593,17 @@ window_pane_find_right(struct window_pane *wp) bottom = wp->yoff + wp->sy; TAILQ_FOREACH(next, &w->panes, entry) { + window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (next->xoff != edge) + if (xoff != edge) continue; - end = next->yoff + next->sy - 1; + end = yoff + sy - 1; found = 0; - if (next->yoff < top && end > bottom) + if (yoff < top && end > bottom) found = 1; - else if (next->yoff >= top && next->yoff <= bottom) + else if (yoff >= top && yoff <= bottom) found = 1; else if (end >= top && end <= bottom) found = 1; @@ -1723,6 +1769,8 @@ window_set_fill_character(struct window *w) ud = utf8_fromcstr(value); if (ud != NULL && ud[0].width == 1) w->fill_character = ud; + else + free(ud); } } @@ -1756,3 +1804,162 @@ window_pane_show_scrollbar(struct window_pane *wp, int sb_option) return (1); return (0); } + +int +window_pane_get_bg(struct window_pane *wp) +{ + int c; + struct grid_cell defaults; + + c = window_pane_get_bg_control_client(wp); + if (c == -1) { + tty_default_colours(&defaults, wp); + if (COLOUR_DEFAULT(defaults.bg)) + c = window_get_bg_client(wp); + else + c = defaults.bg; + } + return (c); +} + +/* Get a client with a background for the pane. */ +int +window_get_bg_client(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.bg == -1) + continue; + return (loop->tty.bg); + } + return (-1); +} + +/* + * If any control mode client exists that has provided a bg color, return it. + * Otherwise, return -1. + */ +int +window_pane_get_bg_control_client(struct window_pane *wp) +{ + struct client *c; + + if (wp->control_bg == -1) + return (-1); + + TAILQ_FOREACH(c, &clients, entry) { + if (c->flags & CLIENT_CONTROL) + return (wp->control_bg); + } + return (-1); +} + +/* + * Get a client with a foreground for the pane. There isn't much to choose + * between them so just use the first. + */ +int +window_pane_get_fg(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.fg == -1) + continue; + return (loop->tty.fg); + } + return (-1); +} + +/* + * If any control mode client exists that has provided a fg color, return it. + * Otherwise, return -1. + */ +int +window_pane_get_fg_control_client(struct window_pane *wp) +{ + struct client *c; + + if (wp->control_fg == -1) + return (-1); + + TAILQ_FOREACH(c, &clients, entry) { + if (c->flags & CLIENT_CONTROL) + return (wp->control_fg); + } + return (-1); +} + +enum client_theme +window_pane_get_theme(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + enum client_theme theme; + int found_light = 0, found_dark = 0; + + /* + * Derive theme from pane background color, if it's not the default + * colour. + */ + theme = colour_totheme(window_pane_get_bg(wp)); + if (theme != THEME_UNKNOWN) + return (theme); + + /* Try to find a client that has a theme. */ + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + switch (loop->theme) { + case THEME_LIGHT: + found_light = 1; + break; + case THEME_DARK: + found_dark = 1; + break; + case THEME_UNKNOWN: + break; + } + } + + if (found_dark && !found_light) + return (THEME_DARK); + if (found_light && !found_dark) + return (THEME_LIGHT); + return (THEME_UNKNOWN); +} + +void +window_pane_send_theme_update(struct window_pane *wp) +{ + if (~wp->flags & PANE_THEMECHANGED) + return; + if (~wp->screen->mode & MODE_THEME_UPDATES) + return; + + switch (window_pane_get_theme(wp)) { + case THEME_LIGHT: + input_key_pane(wp, KEYC_REPORT_LIGHT_THEME, NULL); + break; + case THEME_DARK: + input_key_pane(wp, KEYC_REPORT_DARK_THEME, NULL); + break; + case THEME_UNKNOWN: + break; + } + + wp->flags &= ~PANE_THEMECHANGED; +}