diff --git a/colour.c b/colour.c index 1cad01f91..0db080fc4 100644 --- a/colour.c +++ b/colour.c @@ -26,6 +26,85 @@ #include "tmux.h" +/* Theme colour slots and their server options. */ +static const struct { + const char *name; + const char *dark_option; + const char *light_option; + int terminal_colour; +} colour_theme_table[] = { + { "themeblack", + "dark-theme-black", + "light-theme-black", + 0 + }, + { "themewhite", + "dark-theme-white", + "light-theme-white", + 7 + }, + { "themelightgrey", + "dark-theme-light-grey", + "light-theme-light-grey", + 7 + }, + { "themedarkgrey", + "dark-theme-dark-grey", + "light-theme-dark-grey", + 0 + }, + { "themegreen", + "dark-theme-green", + "light-theme-green", + 2 + }, + { "themeyellow", + "dark-theme-yellow", + "light-theme-yellow", + 3 + }, + { "themered", + "dark-theme-red", + "light-theme-red", + 1 + }, + { "themeblue", + "dark-theme-blue", + "light-theme-blue", + 4 + }, + { "themecyan", + "dark-theme-cyan", + "light-theme-cyan", + 6 + }, + { "thememagenta", + "dark-theme-magenta", + "light-theme-magenta", + 5 + } +}; + +/* Get theme colour option. */ +const char * +colour_theme_option(u_int n, enum client_theme theme) +{ + if (n >= nitems(colour_theme_table)) + return (NULL); + if (theme == THEME_LIGHT) + return (colour_theme_table[n].light_option); + return (colour_theme_table[n].dark_option); +} + +/* Get theme terminal colour. */ +int +colour_theme_terminal_colour(u_int n) +{ + if (n >= nitems(colour_theme_table)) + return (8); + return (colour_theme_table[n].terminal_colour); +} + static int colour_dist_sq(int R, int G, int B, int r, int g, int b) { @@ -126,7 +205,7 @@ colour_dim(int c, u_int dim) { u_char r, g, b; - if (dim == 0 || COLOUR_DEFAULT(c)) + if (dim == 0 || COLOUR_DEFAULT(c) || (c & COLOUR_FLAG_THEME)) return (c); if (dim >= 100) return (colour_join_rgb(0, 0, 0)); @@ -152,6 +231,13 @@ colour_tostring(int c) if (c == -1) return ("none"); + if (c & COLOUR_FLAG_THEME) { + c &= 0xff; + if (c >= 0 && (u_int)c < nitems(colour_theme_table)) + return (colour_theme_table[c].name); + return ("invalid"); + } + if (c & COLOUR_FLAG_RGB) { colour_split_rgb(c, &r, &g, &b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); @@ -252,6 +338,7 @@ colour_fromstring(const char *s) const char *cp; int n; u_char r, g, b; + u_int i; if (*s == '#' && strlen(s) == 7) { for (cp = s + 1; isxdigit((u_char) *cp); cp++) @@ -282,6 +369,11 @@ colour_fromstring(const char *s) if (strcasecmp(s, "terminal") == 0) return (9); + for (i = 0; i < nitems(colour_theme_table); i++) { + if (strcasecmp(s, colour_theme_table[i].name) == 0) + return (i|COLOUR_FLAG_THEME); + } + if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0) return (0); if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0) diff --git a/grid.c b/grid.c index 52da173a1..0796b875c 100644 --- a/grid.c +++ b/grid.c @@ -86,7 +86,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->data.size > 1 || gc->data.width > 1) return (1); - if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB)) + if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) || + (gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME))) return (1); if (gc->us != 8) /* only supports 256 or RGB */ return (1); @@ -218,7 +219,7 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved) if (bg != 8) gee->bg = bg; } else if (bg != 8) { - if (bg & COLOUR_FLAG_RGB) { + if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) { grid_get_extended_cell(gl, gce, gce->flags); gee = grid_extended_cell(gl, gce, &grid_cleared_cell); gee->bg = bg; @@ -798,7 +799,9 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->fg & COLOUR_FLAG_256) { + if (gc->fg & COLOUR_FLAG_THEME) + values[n++] = 39; + else if (gc->fg & COLOUR_FLAG_256) { values[n++] = 38; values[n++] = 5; values[n++] = gc->fg & 0xff; @@ -847,7 +850,9 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->bg & COLOUR_FLAG_256) { + if (gc->bg & COLOUR_FLAG_THEME) + values[n++] = 49; + else if (gc->bg & COLOUR_FLAG_256) { values[n++] = 48; values[n++] = 5; values[n++] = gc->bg & 0xff; @@ -896,7 +901,9 @@ grid_string_cells_us(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->us & COLOUR_FLAG_256) { + if (gc->us & COLOUR_FLAG_THEME) { + values[n++] = 59; + } else if (gc->us & COLOUR_FLAG_256) { values[n++] = 58; values[n++] = 5; values[n++] = gc->us & 0xff; diff --git a/options-table.c b/options-table.c index 6050c24a0..17758d714 100644 --- a/options-table.c +++ b/options-table.c @@ -109,6 +109,9 @@ static const char *options_table_extended_keys_format_list[] = { static const char *options_table_allow_passthrough_list[] = { "off", "on", "all", NULL }; +static const char *options_table_theme_list[] = { + "detect", "terminal", "light", "dark", NULL +}; static const char *options_table_copy_mode_line_numbers_list[] = { "off", "default", "absolute", "relative", "hybrid", NULL }; @@ -446,7 +449,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themewhite", .separator = ",", .text = "Default style of menu." }, @@ -455,7 +458,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "bg=yellow,fg=black", + .default_str = "bg=themeyellow,fg=themeblack", .separator = ",", .text = "Default style of selected menu item." }, @@ -463,7 +466,7 @@ const struct options_table_entry options_table[] = { { .name = "menu-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of menu borders." @@ -538,6 +541,175 @@ const struct options_table_entry options_table[] = { "automatically detected." }, + { .name = "theme", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_theme_list, + .default_num = 0, + .text = "Whether tmux should detect the terminal theme, use terminal " + "ANSI colours, or force the light or dark theme." + }, + + { .name = "dark-theme-black", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray5,black}", + .text = "Dark theme colour for black." + }, + + { .name = "dark-theme-white", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray90,white}", + .text = "Dark theme colour for white." + }, + + { .name = "dark-theme-light-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray70,white}", + .text = "Dark theme colour for light grey." + }, + + { .name = "dark-theme-dark-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray15,black}", + .text = "Dark theme colour for dark grey." + }, + + { .name = "dark-theme-green", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},palegreen,green}", + .text = "Dark theme colour for green." + }, + + { .name = "dark-theme-yellow", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightgoldenrod,yellow}", + .text = "Dark theme colour for yellow." + }, + + { .name = "dark-theme-red", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightsalmon,red}", + .text = "Dark theme colour for red." + }, + + { .name = "dark-theme-blue", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightskyblue,blue}", + .text = "Dark theme colour for blue." + }, + + { .name = "dark-theme-cyan", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},aquamarine,cyan}", + .text = "Dark theme colour for cyan." + }, + + { .name = "dark-theme-magenta", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},plum,magenta}", + .text = "Dark theme colour for magenta." + }, + + { .name = "light-theme-black", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray10,black}", + .text = "Light theme colour for black." + }, + + { .name = "light-theme-white", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray95,white}", + .text = "Light theme colour for white." + }, + + { .name = "light-theme-light-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray80,white}", + .text = "Light theme colour for light grey." + }, + + { .name = "light-theme-dark-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray45,black}", + .text = "Light theme colour for dark grey." + }, + + { .name = "light-theme-green", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},forestgreen,green}", + .text = "Light theme colour for green." + }, + + { .name = "light-theme-yellow", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}", + .text = "Light theme colour for yellow." + }, + + { .name = "light-theme-red", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},violetred4,red}", + .text = "Light theme colour for red." + }, + + { .name = "light-theme-blue", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},royalblue,blue}", + .text = "Light theme colour for blue." + }, + + { .name = "light-theme-cyan", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},darkcyan,cyan}", + .text = "Light theme colour for cyan." + }, + + { .name = "light-theme-magenta", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},purple,magenta}", + .text = "Light theme colour for magenta." + }, + { .name = "user-keys", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, @@ -639,7 +811,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "red", + .default_str = "themered", .text = "Colour of the active pane for 'display-panes'." }, @@ -647,7 +819,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "blue", + .default_str = "themeblue", .text = "Colour of not active panes for 'display-panes'." }, @@ -738,7 +910,7 @@ const struct options_table_entry options_table[] = { { .name = "message-command-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=black,fg=yellow,fill=black", + .default_str = "bg=themegreen,fg=themeblack,fill=themegreen", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the command prompt when in command mode, if " @@ -767,9 +939,9 @@ const struct options_table_entry options_table[] = { { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black," + .default_str = "bg=themeyellow,fg=themeblack," "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," - "fill=yellow}", + "fill=themeyellow}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and the command prompt. " @@ -968,7 +1140,7 @@ const struct options_table_entry options_table[] = { { .name = "status-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=green,fg=black", + .default_str = "bg=themegreen,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the status line." @@ -1152,7 +1324,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "blue", + .default_str = "themeblue", .text = "Colour of the clock in clock mode." }, @@ -1167,7 +1339,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=cyan,fg=black", + .default_str = "bg=themecyan,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of search matches in copy mode." @@ -1176,7 +1348,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-current-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=magenta,fg=black", + .default_str = "bg=thememagenta,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the current search match in copy mode." @@ -1185,7 +1357,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-mark-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=red,fg=black", + .default_str = "bg=themeyellow,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the marked line in copy mode." @@ -1224,7 +1396,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-current-line-number-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=yellow", + .default_str = "fg=themeyellow", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of current line number in copy mode." @@ -1233,7 +1405,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-line-number-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=white,dim", + .default_str = "fg=themelightgrey,dim", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of line numbers in copy mode." @@ -1282,7 +1454,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "noattr,bg=yellow,fg=black", + .default_str = "noattr,bg=themeyellow,fg=themeblack", .separator = ",", .text = "Style of indicators and highlighting in modes." }, @@ -1331,7 +1503,9 @@ const struct options_table_entry options_table[] = { { .name = "pane-active-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", + .default_str = "fg=#{?pane_marked,thememagenta," + "#{?synchronize-panes,themered," + "#{?pane_in_mode,themeyellow,themegreen}}}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the active pane border." @@ -1390,7 +1564,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default", + .default_str = "fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the pane status lines." @@ -1415,7 +1589,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-scrollbars-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "bg=black,fg=white,width=1,pad=0", + .default_str = "bg=themedarkgrey,fg=themelightgrey,width=1,pad=0", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the pane scrollbar." @@ -1432,7 +1606,7 @@ const struct options_table_entry options_table[] = { { .name = "popup-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themewhite", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popups." @@ -1441,7 +1615,7 @@ const struct options_table_entry options_table[] = { { .name = "popup-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popup borders." @@ -1519,8 +1693,8 @@ const struct options_table_entry options_table[] = { .default_str = "fg=#{?#{||:" "#{&&:#{pane_format},#{pane_active}}," "#{&&:#{window_format},#{window_active}}}," - "#{display-panes-active-colour}," - "#{display-panes-colour}}", + "themered," + "themeblue}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of preview indicator in tree mode." diff --git a/options.c b/options.c index b9e319acf..db0f221a3 100644 --- a/options.c +++ b/options.c @@ -1214,6 +1214,17 @@ options_push_changes(const char *name) log_debug("%s: %s", __func__, name); + if (strcmp(name, "theme") == 0 || + strncmp(name, "dark-theme-", 11) == 0 || + strncmp(name, "light-theme-", 12) == 0) { + TAILQ_FOREACH(loop, &clients, entry) { + server_client_update_theme_colours(loop); + if (loop->tty.flags & TTY_OPENED) + tty_invalidate(&loop->tty); + server_redraw_client(loop); + } + } + if (strcmp(name, "automatic-rename") == 0) { RB_FOREACH(w, windows, &windows) { if (w->active == NULL) diff --git a/server-client.c b/server-client.c index 60fdab2a6..5b2651658 100644 --- a/server-client.c +++ b/server-client.c @@ -291,6 +291,7 @@ struct client * server_client_create(int fd) { struct client *c; + u_int i; setblocking(fd, 0); @@ -313,6 +314,9 @@ server_client_create(int fd) c->tty.sx = 80; c->tty.sy = 24; + + for (i = 0; i < COLOUR_THEME_COUNT; i++) + c->theme_colours[i] = 8; c->theme = THEME_UNKNOWN; status_init(c); @@ -364,6 +368,7 @@ server_client_open(struct client *c, char **cause) if (tty_open(&c->tty, cause) != 0) return (-1); + server_client_update_theme_colours(c); return (0); } @@ -1081,6 +1086,52 @@ have_event: return (key); } +/* Update client theme colours from server options. */ +void +server_client_update_theme_colours(struct client *c) +{ + struct format_tree *ft; + const char *name, *value; + enum client_theme theme; + char *expanded; + u_int i; + int colour, option; + + if (c == NULL) + return; + + option = options_get_number(global_options, "theme"); + if (option == 1) { + for (i = 0; i < COLOUR_THEME_COUNT; i++) + c->theme_colours[i] = colour_theme_terminal_colour(i); + return; + } + + ft = format_create(c, NULL, FORMAT_NONE, FORMAT_NOJOBS); + format_defaults(ft, c, NULL, NULL, NULL); + + theme = c->theme; + if (option == 2) + theme = THEME_LIGHT; + else if (option == 3) + theme = THEME_DARK; + for (i = 0; i < COLOUR_THEME_COUNT; i++) { + c->theme_colours[i] = 8; + name = colour_theme_option(i, theme); + if (name == NULL) + continue; + value = options_get_string(global_options, name); + expanded = format_expand(ft, value); + colour = colour_fromstring(expanded); + free(expanded); + if (colour == -1 || (colour & COLOUR_FLAG_THEME)) + continue; + c->theme_colours[i] = colour; + } + + format_free(ft); +} + /* Is this a bracket paste key? */ static int server_client_is_bracket_paste(struct client *c, key_code key) @@ -2928,6 +2979,8 @@ out: static void server_client_report_theme(struct client *c, enum client_theme theme) { + enum client_theme old = c->theme; + if (theme == THEME_LIGHT) { c->theme = THEME_LIGHT; notify_client("client-light-theme", c); @@ -2936,6 +2989,17 @@ server_client_report_theme(struct client *c, enum client_theme theme) notify_client("client-dark-theme", c); } + /* + * If the theme has changed, update the theme colours and redraw the + * client. + */ + if (c->theme != old) { + server_client_update_theme_colours(c); + if (c->tty.flags & TTY_OPENED) + tty_invalidate(&c->tty); + server_redraw_client(c); + } + /* * Request foreground and background colour again. Don't forward 2031 to * panes until a response is received. diff --git a/tmux.1 b/tmux.1 index 1f96045a2..fe5265e50 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4571,6 +4571,57 @@ where the number is a hexadecimal number, or a range of the form Give the command to pipe to if the .Ic copy\-pipe copy mode command is used without arguments. +.It Xo Ic theme +.Op Ic detect | terminal | light | dark +.Xc +Whether tmux should use the detected client theme +.Pq Ic detect , +use terminal ANSI colours +.Pq Ic terminal , +or force the light or dark theme. +.It Xo Ic dark\-theme\-black , +.Ic dark\-theme\-white , +.Ic dark\-theme\-light\-grey , +.Ic dark\-theme\-dark\-grey , +.Ic dark\-theme\-green , +.Ic dark\-theme\-yellow , +.Ic dark\-theme\-red , +.Ic dark\-theme\-blue , +.Ic dark\-theme\-cyan , +.Ic dark\-theme\-magenta , +.Ic light\-theme\-black , +.Ic light\-theme\-white , +.Ic light\-theme\-light\-grey , +.Ic light\-theme\-dark\-grey , +.Ic light\-theme\-green , +.Ic light\-theme\-yellow , +.Ic light\-theme\-red , +.Ic light\-theme\-blue , +.Ic light\-theme\-cyan +.No and Ic light\-theme\-magenta Ar colour +.Xc +Set the theme colours. +These are the named colours +.Ic themeblack , +.Ic themewhite , +.Ic themelightgrey , +.Ic themedarkgrey , +.Ic themegreen , +.Ic themeyellow , +.Ic themered , +.Ic themeblue , +.Ic themecyan +and +.Ic thememagenta +which may be used in styles and elsewhere a +.Ar colour +is accepted. +The +.Ic dark\-theme\-* +set is used when the client reports a dark background and the +.Ic light\-theme\-* +set when it reports a light background; +an unknown background is treated as dark. .It Ic default\-client\-command Ar command Set the default command to run when tmux is called without a command. The default is diff --git a/tmux.h b/tmux.h index bbf25b9c8..3ebf8ce70 100644 --- a/tmux.h +++ b/tmux.h @@ -728,10 +728,26 @@ enum hanguljamo_state { /* Colour flags. */ #define COLOUR_FLAG_256 0x01000000 #define COLOUR_FLAG_RGB 0x02000000 +#define COLOUR_FLAG_THEME 0x04000000 /* Special colours. */ #define COLOUR_DEFAULT(c) ((c) == 8 || (c) == 9) +/* Theme colours. */ +enum colour_theme { + COLOUR_THEME_BLACK, + COLOUR_THEME_WHITE, + COLOUR_THEME_LIGHT_GREY, + COLOUR_THEME_DARK_GREY, + COLOUR_THEME_GREEN, + COLOUR_THEME_YELLOW, + COLOUR_THEME_RED, + COLOUR_THEME_BLUE, + COLOUR_THEME_CYAN, + COLOUR_THEME_MAGENTA +}; +#define COLOUR_THEME_COUNT 10 + /* Replacement palette. */ struct colour_palette { int fg; @@ -2219,6 +2235,8 @@ struct client { int references; + int theme_colours[COLOUR_THEME_COUNT]; + void *pan_window; u_int pan_ox; u_int pan_oy; @@ -3113,6 +3131,7 @@ void server_client_print(struct client *, int, struct evbuffer *); /* server-fn.c */ void server_redraw_client(struct client *); +void server_client_update_theme_colours(struct client *); void server_status_client(struct client *); void server_redraw_session(struct session *); void server_redraw_session_group(struct session *); @@ -3226,6 +3245,8 @@ int colour_dim(int, u_int); const char *colour_tostring(int); enum client_theme colour_totheme(int); int colour_fromstring(const char *); +const char *colour_theme_option(u_int, enum client_theme); +int colour_theme_terminal_colour(u_int); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); diff --git a/tty.c b/tty.c index 15cab4bfa..fa1b45076 100644 --- a/tty.c +++ b/tty.c @@ -51,6 +51,7 @@ static void tty_check_bg(struct tty *, struct colour_palette *, struct grid_cell *); static void tty_check_us(struct tty *, struct colour_palette *, struct grid_cell *); +static int tty_map_theme_colour(struct tty *, int); static void tty_colours_fg(struct tty *, const struct grid_cell *); static void tty_colours_bg(struct tty *, const struct grid_cell *); static void tty_colours_us(struct tty *, const struct grid_cell *); @@ -2517,6 +2518,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, gc2.bg = changed; } } + gc2.fg = tty_map_theme_colour(tty, gc2.fg); + gc2.bg = tty_map_theme_colour(tty, gc2.bg); + gc2.us = tty_map_theme_colour(tty, gc2.us); if (style_ctx->dim != 0) { gc2.fg = tty_dim_default_colour(tty, gc2.fg, 1); gc2.bg = tty_dim_default_colour(tty, gc2.bg, 0); @@ -2666,6 +2670,28 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) tty_colours_us(tty, gc); } +static int +tty_map_theme_colour(struct tty *tty, int colour) +{ + struct client *c; + u_int n; + int m; + + if (~colour & COLOUR_FLAG_THEME) + return (colour); + + n = colour & 0xff; + if (n >= COLOUR_THEME_COUNT) + return (8); + if (tty == NULL || (c = tty->client) == NULL) + return (8); + + m = c->theme_colours[n]; + if (m == -1 || (m & COLOUR_FLAG_THEME)) + return (8); + return (m); +} + static void tty_check_fg(struct tty *tty, struct colour_palette *palette, struct grid_cell *gc) @@ -2688,6 +2714,7 @@ tty_check_fg(struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, c)) != -1) gc->fg = c; } + gc->fg = tty_map_theme_colour(tty, gc->fg); /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { @@ -2748,6 +2775,7 @@ tty_check_bg(struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, gc->bg)) != -1) gc->bg = c; } + gc->bg = tty_map_theme_colour(tty, gc->bg); /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { @@ -2798,6 +2826,7 @@ tty_check_us(__unused struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, gc->us)) != -1) gc->us = c; } + gc->us = tty_map_theme_colour(tty, gc->us); /* Convert underscore colour if only RGB can be supported. */ if (!tty_term_has(tty->term, TTYC_SETULC1)) {