From 22ce09eb44db887d32a5b3a88740939b7285c763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Mar 2020 18:42:49 +0100 Subject: [PATCH] config: make CSD user configurable The user can now configure the following: * Whether to prefer CSDs or SSDs. But note that this is only a hint to the compositor - it may deny our request. Furthermore, not all compositors implement the decoration manager protocol, meaning CSDs will be used regardless of the user configuration (GNOME/mutter being the most prominent one). * Title bar size and color, including transparency * Border size and color, including transparency Also drop support for rendering the CSDs inside the main surface. --- config.c | 90 +++++++++++++++++++++++++++++++++++++++++--- config.h | 14 +++++++ doc/foot.5.scd | 31 +++++++++++++++ footrc | 7 ++++ input.c | 31 ++++++++------- render.c | 100 +++++++++++++++++++------------------------------ wayland.c | 35 +++++++++-------- wayland.h | 9 ----- 8 files changed, 210 insertions(+), 107 deletions(-) diff --git a/config.c b/config.c index 37795bb9..246e0d18 100644 --- a/config.c +++ b/config.c @@ -133,7 +133,7 @@ str_to_double(const char *s, double *res) } static bool -str_to_color(const char *s, uint32_t *color, const char *path, int lineno) +str_to_color(const char *s, uint32_t *color, bool allow_alpha, const char *path, int lineno) { unsigned long value; if (!str_to_ulong(s, 16, &value)) { @@ -141,7 +141,12 @@ str_to_color(const char *s, uint32_t *color, const char *path, int lineno) return false; } - *color = value & 0xffffff; + if (!allow_alpha && (value & 0xff000000) != 0) { + LOG_ERR("%s:%d: color value must not have an alpha component", path, lineno); + return false; + } + + *color = value; return true; } @@ -273,7 +278,7 @@ parse_section_colors(const char *key, const char *value, struct config *conf, } uint32_t color_value; - if (!str_to_color(value, &color_value, path, lineno)) + if (!str_to_color(value, &color_value, false, path, lineno)) return false; *color = color_value; @@ -305,8 +310,8 @@ parse_section_cursor(const char *key, const char *value, struct config *conf, uint32_t text_color, cursor_color; if (text == NULL || cursor == NULL || - !str_to_color(text, &text_color, path, lineno) || - !str_to_color(cursor, &cursor_color, path, lineno)) + !str_to_color(text, &text_color, false, path, lineno) || + !str_to_color(cursor, &cursor_color, false, path, lineno)) { LOG_ERR("%s:%d: invalid cursor colors: %s", path, lineno, value); free(value_copy); @@ -326,6 +331,66 @@ parse_section_cursor(const char *key, const char *value, struct config *conf, return true; } +static bool +parse_section_csd(const char *key, const char *value, struct config *conf, + const char *path, unsigned lineno) +{ + if (strcmp(key, "preferred") == 0) { + if (strcmp(value, "server") == 0) + conf->csd.preferred = CONF_CSD_PREFER_SERVER; + else if (strcmp(value, "client") == 0) + conf->csd.preferred = CONF_CSD_PREFER_CLIENT; + else { + LOG_ERR("%s:%d: expected either 'server' or 'client'", path, lineno); + return false; + } + } + + else if (strcmp(key, "titlebar-color") == 0) { + uint32_t color; + if (!str_to_color(value, &color, true, path, lineno)) { + LOG_ERR("%s:%d: invalid titlebar-color: %s", path, lineno, value); + return false; + } + + conf->csd.color.title_set = true; + conf->csd.color.title = color; + } + + else if (strcmp(key, "border-color") == 0) { + uint32_t color; + if (!str_to_color(value, &color, true, path, lineno)) { + LOG_ERR("%s:%d: invalid border-color: %s", path, lineno, value); + return false; + } + + conf->csd.color.border_set = true; + conf->csd.color.border = color; + } + + else if (strcmp(key, "titlebar") == 0) { + unsigned long pixels; + if (!str_to_ulong(value, 10, &pixels)) { + LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value); + return false; + } + + conf->csd.title_height = pixels; + } + + else if (strcmp(key, "border") == 0) { + unsigned long pixels; + if (!str_to_ulong(value, 10, &pixels)) { + LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value); + return false; + } + + conf->csd.border_width = pixels; + } + + return true; +} + static bool parse_config_file(FILE *f, struct config *conf, const char *path) { @@ -333,6 +398,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) SECTION_MAIN, SECTION_COLORS, SECTION_CURSOR, + SECTION_CSD, } section = SECTION_MAIN; /* Function pointer, called for each key/value line */ @@ -345,6 +411,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) [SECTION_MAIN] = &parse_section_main, [SECTION_COLORS] = &parse_section_colors, [SECTION_CURSOR] = &parse_section_cursor, + [SECTION_CSD] = &parse_section_csd, }; #if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG @@ -352,6 +419,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) [SECTION_MAIN] = "main", [SECTION_COLORS] = "colors", [SECTION_CURSOR] = "cursor", + [SECTION_CSD] = "csd", }; #endif @@ -408,6 +476,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path) section = SECTION_COLORS; else if (strcmp(&line[1], "cursor") == 0) section = SECTION_CURSOR; + else if (strcmp(&line[1], "csd") == 0) + section = SECTION_CSD; else { LOG_ERR("%s:%d: invalid section name: %s", path, lineno, &line[1]); goto err; @@ -532,6 +602,16 @@ config_load(struct config *conf, const char *conf_path) }, }, + .csd = { + .preferred = CONF_CSD_PREFER_SERVER, + .title_height = 26, + .border_width = 5, + .color = { + .title_set = false, + .border_set = false, + }, + }, + .render_worker_count = sysconf(_SC_NPROCESSORS_ONLN), .server_socket_path = get_server_socket_path(), .presentation_timings = false, diff --git a/config.h b/config.h index 3677129f..fa765476 100644 --- a/config.h +++ b/config.h @@ -36,6 +36,20 @@ struct config { } color; } cursor; + struct { + enum { CONF_CSD_PREFER_SERVER, CONF_CSD_PREFER_CLIENT } preferred; + + int title_height; + int border_width; + + struct { + bool title_set; + bool border_set; + uint32_t title; + uint32_t border; + } color; + } csd; + size_t render_worker_count; char *server_socket_path; bool presentation_timings; diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 41785366..30fd735c 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -98,6 +98,37 @@ in this order: Background translucency. A value in the range 0.0-1.0, where 0.0 means completely transparent, and 1.0 is opaque. Default: _1.0_. +# SECTION: csd + +This section controls the look of the _CSDs_ (Client Side +Decorations). Note that the default is to *not* use CSDs, but instead +to use _SSDs_ (Server Side Decorations) when the compositor supports +it. + +*preferred* + Which type of window decorations to prefer: *client* (CSD) or + *server* (SSD). Note that this is only a hint to the + compositor. Depending on the compositor's configuration and + capabilities, it may not have any effect. Default: _server_. + +*titlebar* + Height, in pixels (but subject to output scaling), of the + titlebar, not including the window border. Default: _26_. + +*border* + Width, in pixels (but subject to output scaling), of the + borders. Default: _5_. + +*titlebar-color* + Titlebar AARRGGBB color. Note that unlike the other color values, + the *titlebar-color* value also has an _alpha_ component. Default: + use the default _foreground_ color. + +*border-color* + Border AARRGGBB color. Note that unlike the other color values, + the *border-color* value also has an _alpha_ component. Default: + _transparent_. + # FONT FORMAT The font is specified in FontConfig syntax. That is, a colon-separated diff --git a/footrc b/footrc index e6010eb2..7eea3ada 100644 --- a/footrc +++ b/footrc @@ -33,3 +33,10 @@ # bright6=b3ffff # bright7=ffffff # alpha=1.0 + +[csd] +# preferred=server +# titlebar=26 +# border=5 +# titlebar-color= +# border-color=00000000 diff --git a/input.c b/input.c index 2fde9ca4..dbb8ec33 100644 --- a/input.c +++ b/input.c @@ -21,6 +21,7 @@ #define LOG_MODULE "input" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "config.h" #include "commands.h" #include "keymap.h" #include "render.h" @@ -616,6 +617,7 @@ input_repeat(struct wayland *wayl, uint32_t key) static bool is_top_left(const struct terminal *term, int x, int y) { + int csd_border_size = term->conf->csd.border_width; return ( (term->active_surface == TERM_SURF_BORDER_LEFT && y < 10 * term->scale) || (term->active_surface == TERM_SURF_BORDER_TOP && x < (10 + csd_border_size) * term->scale)); @@ -624,6 +626,7 @@ is_top_left(const struct terminal *term, int x, int y) static bool is_top_right(const struct terminal *term, int x, int y) { + int csd_border_size = term->conf->csd.border_width; return ( (term->active_surface == TERM_SURF_BORDER_RIGHT && y < 10 * term->scale) || (term->active_surface == TERM_SURF_BORDER_TOP && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale)); @@ -632,6 +635,8 @@ is_top_right(const struct terminal *term, int x, int y) static bool is_bottom_left(const struct terminal *term, int x, int y) { + int csd_title_size = term->conf->csd.title_height; + int csd_border_size = term->conf->csd.border_width; return ( (term->active_surface == TERM_SURF_BORDER_LEFT && y > csd_title_size * term->scale + term->height) || (term->active_surface == TERM_SURF_BORDER_BOTTOM && x < (10 + csd_border_size) * term->scale)); @@ -640,6 +645,8 @@ is_bottom_left(const struct terminal *term, int x, int y) static bool is_bottom_right(const struct terminal *term, int x, int y) { + int csd_title_size = term->conf->csd.title_height; + int csd_border_size = term->conf->csd.border_width; return ( (term->active_surface == TERM_SURF_BORDER_RIGHT && y > csd_title_size * term->scale + term->height) || (term->active_surface == TERM_SURF_BORDER_BOTTOM && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale)); @@ -648,22 +655,14 @@ is_bottom_right(const struct terminal *term, int x, int y) static const char * xcursor_for_csd_border(struct terminal *term, int x, int y) { - if (is_top_left(term, x, y)) - return "top_left_corner"; - else if (is_top_right(term, x, y)) - return "top_right_corner"; - else if (is_bottom_left(term, x, y)) - return "bottom_left_corner"; - else if (is_bottom_right(term, x, y)) - return "bottom_right_corner"; - else if (term->active_surface == TERM_SURF_BORDER_LEFT) - return "left_side"; - else if (term->active_surface == TERM_SURF_BORDER_RIGHT) - return "right_side"; - else if (term->active_surface == TERM_SURF_BORDER_TOP) - return "top_side"; - else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) - return"bottom_side"; + if (is_top_left(term, x, y)) return "top_left_corner"; + else if (is_top_right(term, x, y)) return "top_right_corner"; + else if (is_bottom_left(term, x, y)) return "bottom_left_corner"; + else if (is_bottom_right(term, x, y)) return "bottom_right_corner"; + else if (term->active_surface == TERM_SURF_BORDER_LEFT) return "left_side"; + else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return "right_side"; + else if (term->active_surface == TERM_SURF_BORDER_TOP) return "top_side"; + else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return"bottom_side"; else { assert(false); return NULL; diff --git a/render.c b/render.c index 740d3e55..f08c4cf4 100644 --- a/render.c +++ b/render.c @@ -25,9 +25,6 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) -const int csd_border_size = 5; -const int csd_title_size = 26; - struct renderer { struct fdm *fdm; struct wayland *wayl; @@ -680,12 +677,11 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) /* Only title bar is rendered in maximized mode */ const int border_width = !term->window->is_maximized - ? csd_border_size * term->scale : 0; + ? term->conf->csd.border_width * term->scale : 0; const int title_height = !term->window->is_fullscreen - ? csd_title_size * term->scale : 0; + ? term->conf->csd.title_height * term->scale : 0; -#if FOOT_CSD_OUTSIDE switch (surf_idx) { case CSD_SURF_TITLE: return (struct csd_data){ 0, -title_height, term->width, title_height}; case CSD_SURF_LEFT: return (struct csd_data){-border_width, -title_height, border_width, title_height + term->height}; @@ -697,18 +693,6 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) assert(false); return (struct csd_data){0}; } -#else - switch (surf_idx) { - case CSD_SURF_TITLE: return (struct csd_data){ border_width, border_width, term->width - 2 * border_width, title_height}; - case CSD_SURF_LEFT: return (struct csd_data){ 0, border_width, border_width, term->height - 2 * border_width}; - case CSD_SURF_RIGHT: return (struct csd_data){term->width - border_width, border_width, border_width, term->height - 2 * border_width}; - case CSD_SURF_TOP: return (struct csd_data){ 0, 0, term->width, border_width}; - - case CSD_SURF_COUNT: - assert(false); - return (struct csd_data){0}; - } -#endif assert(false); return (struct csd_data){0}; @@ -749,18 +733,18 @@ render_csd_title(struct terminal *term) struct buffer *buf = shm_get_buffer( term->wl->shm, info.width, info.height, cookie); - pixman_color_t color = color_hex_to_pixman(term->colors.fg); + uint32_t _color = term->colors.fg; + uint16_t alpha = 0xffff; + if (term->conf->csd.color.title_set) { + _color = term->conf->csd.color.title; + alpha = _color >> 24 | (_color >> 24 << 8); + } + + pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); if (!term->visual_focus) pixman_color_dim(&color); - struct wl_region *region = wl_compositor_create_region(term->wl->compositor); - if (region != NULL) { - wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_set_opaque_region(surf, region); - wl_region_destroy(region); - } - render_csd_part(term, surf, buf, info.width, info.height, &color); } @@ -782,8 +766,15 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx) struct buffer *buf = shm_get_buffer( term->wl->shm, info.width, info.height, cookie); - pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0); + uint32_t _color = 0; + uint16_t alpha = 0; + if (term->conf->csd.color.border_set) { + _color = term->conf->csd.color.border; + alpha = _color >> 24 | (_color >> 24 << 8); + } + + pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); if (!term->visual_focus) pixman_color_dim(&color); render_csd_part(term, surf, buf, info.width, info.height, &color); @@ -1145,12 +1136,7 @@ render_search_box(struct terminal *term) assert(term->scale >= 1); const int scale = term->scale; -#if FOOT_CSD_OUTSIDE - const int csd = 0; -#else - const int csd = term->window->use_csd == CSD_YES ? csd_border_size * scale : 0; -#endif - const size_t margin = csd + 3 * scale; + const size_t margin = 3 * scale; const size_t width = term->width - 2 * margin; const size_t visible_width = min( @@ -1264,15 +1250,6 @@ maybe_resize(struct terminal *term, int width, int height, bool force) width *= scale; height *= scale; - /* Scaled CSD border + title bar sizes */ -#if FOOT_CSD_OUTSIDE - const int csd_border = 0; - const int csd_title = 0; -#else - const int csd_border = term->window->use_csd == CSD_YES ? csd_border_size * scale : 0; - const int csd_title = term->window->use_csd == CSD_YES ? csd_title_size * scale : 0; -#endif - if (width == 0 && height == 0) { /* * The compositor is letting us choose the size @@ -1293,10 +1270,10 @@ maybe_resize(struct terminal *term, int width, int height, bool force) /* Account for CSDs, to make actual window size match * the configured size */ if (!term->window->is_maximized) { - width -= 2 * csd_border_size; - height -= 2 * csd_border_size + csd_title_size; + width -= 2 * term->conf->csd.border_width; + height -= 2 * term->conf->csd.border_width + term->conf->csd.title_height; } else { - height -= csd_title_size; + height -= term->conf->csd.title_height; } } @@ -1305,24 +1282,23 @@ maybe_resize(struct terminal *term, int width, int height, bool force) } } - const int csd_x = 2 * csd_border; - const int csd_y = 2 * csd_border + csd_title; - - /* Padding */ - const int pad_x = scale * term->conf->pad_x; - const int pad_y = scale * term->conf->pad_y; - /* Don't shrink grid too much */ const int min_cols = 20; const int min_rows = 4; /* Minimum window size */ - const int min_width = csd_x + 2 * pad_x + min_cols * term->cell_width; - const int min_height = csd_y + 2 * pad_y + min_rows * term->cell_height; + const int min_width = min_cols * term->cell_width; + const int min_height = min_rows * term->cell_height; width = max(width, min_width); height = max(height, min_height); + /* Padding */ + const int max_pad_x = (width - min_width) / 2; + const int max_pad_y = (height - min_height) / 2; + const int pad_x = min(max_pad_x, scale * term->conf->pad_x); + const int pad_y = min(max_pad_y, scale * term->conf->pad_y); + if (!force && width == term->width && height == term->height && scale == term->scale) return false; @@ -1342,8 +1318,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const int old_rows = term->rows; /* Screen rows/cols after resize */ - const int new_cols = (term->width - 2 * pad_x - csd_x) / term->cell_width; - const int new_rows = (term->height - 2 * pad_y - csd_y) / term->cell_height; + const int new_cols = (term->width - 2 * pad_x) / term->cell_width; + const int new_rows = (term->height - 2 * pad_y) / term->cell_height; /* Grid rows/cols after resize */ const int new_normal_grid_rows = 1 << (32 - __builtin_clz(new_rows + scrollback_lines - 1)); @@ -1353,15 +1329,15 @@ maybe_resize(struct terminal *term, int width, int height, bool force) assert(new_rows >= 1); /* Margins */ - term->margins.left = csd_border + pad_x; - term->margins.top = csd_border + csd_title + pad_y; + term->margins.left = pad_x; + term->margins.top = pad_y; term->margins.right = term->width - new_cols * term->cell_width - term->margins.left; term->margins.bottom = term->height - new_rows * term->cell_height - term->margins.top; - assert(term->margins.left >= csd_border + pad_x); - assert(term->margins.right >= csd_border + pad_x); - assert(term->margins.top >= csd_border + csd_title + pad_y); - assert(term->margins.bottom >= csd_border + pad_y); + assert(term->margins.left >= pad_x); + assert(term->margins.right >= pad_x); + assert(term->margins.top >= pad_y); + assert(term->margins.bottom >= pad_y); if (new_cols == old_cols && new_rows == old_rows) { LOG_DBG("grid layout unaffected; skipping reflow"); diff --git a/wayland.c b/wayland.c index 957f736e..c5272994 100644 --- a/wayland.c +++ b/wayland.c @@ -536,18 +536,14 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, * sub-surfaces. Thus, since our resize code assumes the size * to resize to is the main window only, adjust the size here, * to account for the CSDs. - * - * Of course this does *not* apply when we position the CSDs - * *inside* the main surface. */ -#if FOOT_CSD_OUTSIDE + const struct config *conf = win->term->conf; if (!is_maximized) { - width -= 2 * csd_border_size; - height -= 2 * csd_border_size + csd_title_size; + width -= 2 * conf->csd.border_width; + height -= 2 * conf->csd.border_width + conf->csd.title_height; } else { - height -= csd_title_size; + height -= conf->csd.title_height; } -#endif } win->configure.is_activated = is_activated; @@ -670,18 +666,18 @@ xdg_toplevel_decoration_configure(void *data, if (win->is_configured && win->use_csd == CSD_YES) { struct terminal *term = win->term; - int scale = term->scale; + const struct config *conf = term->conf; + int scale = term->scale; int width = term->width / scale; int height = term->height / scale; -#if FOOT_CSD_OUTSIDE if (!term->window->is_maximized) { - width -= 2 * csd_border_size; - height -= 2 * csd_border_size + csd_title_size; + width -= 2 * conf->csd.border_width; + height -= 2 * conf->csd.border_width + conf->csd.title_height; } else - height -= csd_title_size; -#endif + height -= conf->csd.title_height; + render_resize_force(term, width, height); } } @@ -1013,6 +1009,7 @@ struct wl_window * wayl_win_init(struct terminal *term) { struct wayland *wayl = term->wl; + const struct config *conf = wayl->conf; struct wl_window *win = calloc(1, sizeof(*win)); win->term = term; @@ -1050,8 +1047,16 @@ wayl_win_init(struct terminal *term) if (wayl->xdg_decoration_manager != NULL) { win->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wayl->xdg_decoration_manager, win->xdg_toplevel); + + LOG_INFO("preferring %s decorations", + conf->csd.preferred == CONF_CSD_PREFER_SERVER ? "SSD" : "CSD"); + zxdg_toplevel_decoration_v1_set_mode( - win->xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + win->xdg_toplevel_decoration, + (conf->csd.preferred == CONF_CSD_PREFER_SERVER + ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE + : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE)); + zxdg_toplevel_decoration_v1_add_listener( win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, win); } else { diff --git a/wayland.h b/wayland.h index b879c319..f94ad0e5 100644 --- a/wayland.h +++ b/wayland.h @@ -82,15 +82,6 @@ struct wl_primary { uint32_t serial; }; -/* I'd prefer to position the CSD sub-surfaces outside the main - * surface. Unfortunately, a lot of compositors doesn't handle this - * correctly. When this define is 0, we instead position the CSD - * sub-surfaces inside the main surface, and offset the grid content - * accordingly. */ -#define FOOT_CSD_OUTSIDE 1 -extern const int csd_border_size; -extern const int csd_title_size; - enum csd_surface { CSD_SURF_TITLE, CSD_SURF_LEFT,