diff --git a/CHANGELOG.md b/CHANGELOG.md index c9dfdecb..84d333f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ ## Unreleased ### Added + +* Window title in the CSDs + (https://codeberg.org/dnkl/foot/issues/638). + + ### Changed ### Deprecated ### Removed diff --git a/config.c b/config.c index a9acb58e..f671e7e0 100644 --- a/config.c +++ b/config.c @@ -503,6 +503,52 @@ str_to_pt_or_px(const char *s, struct pt_or_px *res, struct config *conf, return true; } +static struct config_font_list NOINLINE +str_to_fonts(const char *s, struct config *conf, const char *path, int lineno, + const char *section, const char *key) +{ + size_t count = 0; + size_t size = 0; + struct config_font *fonts = NULL; + + char *copy = xstrdup(s); + for (const char *font = strtok(copy, ","); + font != NULL; + font = strtok(NULL, ",")) + { + /* Trim spaces, strictly speaking not necessary, but looks nice :) */ + while (*font != '\0' && isspace(*font)) + font++; + + if (font[0] == '\0') + continue; + + struct config_font font_data; + if (!config_font_parse(font, &font_data)) { + LOG_AND_NOTIFY_ERR( + "%s:%d: [%s]: %s: invalid font specification: %s", + path, lineno, section, key, font); + goto err; + } + + if (count + 1 > size) { + size += 4; + fonts = xrealloc(fonts, size * sizeof(fonts[0])); + } + + xassert(count + 1 <= size); + fonts[count++] = font_data; + } + + free(copy); + return (struct config_font_list){.arr = fonts, .count = count}; + +err: + free(copy); + free(fonts); + return (struct config_font_list){.arr = NULL, .count = 0}; +} + static void NOINLINE free_argv(struct argv *argv) { @@ -804,8 +850,9 @@ parse_section_main(const char *key, const char *value, struct config *conf, struct config_font font_data; if (!config_font_parse(font, &font_data)) { - LOG_ERR("%s:%d: [default]: %s: invalid font specification", - path, lineno, key); + LOG_AND_NOTIFY_ERR( + "%s:%d: [default]: %s: invalid font specification: %s", + path, lineno, key, font); free(copy); return false; } @@ -1328,6 +1375,17 @@ parse_section_csd(const char *key, const char *value, struct config *conf, } } + else if (strcmp(key, "font") == 0) { + struct config_font_list new_list = str_to_fonts( + value, conf, path, lineno, "csd", "font"); + + if (new_list.arr == NULL) + return false; + + config_font_list_destroy(&conf->csd.font); + conf->csd.font = new_list; + } + else if (strcmp(key, "color") == 0) { uint32_t color; if (!str_to_color(value, &color, true, conf, path, lineno, "csd", "color")) @@ -2706,6 +2764,20 @@ add_default_mouse_bindings(struct config *conf) memcpy(conf->bindings.mouse.arr, bindings, sizeof(bindings)); } +static void NOINLINE +config_font_list_clone(struct config_font_list *dst, + const struct config_font_list *src) +{ + dst->count = src->count; + dst->arr = xmalloc(dst->count * sizeof(dst->arr[0])); + + for (size_t j = 0; j < dst->count; j++) { + dst->arr[j].pt_size = src->arr[j].pt_size; + dst->arr[j].px_size = src->arr[j].px_size; + dst->arr[j].pattern = xstrdup(src->arr[j].pattern); + } +} + bool config_load(struct config *conf, const char *conf_path, user_notifications_t *initial_user_notifications, @@ -2811,6 +2883,7 @@ config_load(struct config *conf, const char *conf_path, }, .csd = { .preferred = CONF_CSD_PREFER_SERVER, + .font = {0}, .title_height = 26, .border_width = 5, .button_width = 26, @@ -2955,6 +3028,9 @@ out: } } + if (ret && conf->csd.font.count == 0) + config_font_list_clone(&conf->csd.font, &conf->fonts[0]); + #if defined(_DEBUG) for (size_t i = 0; i < conf->bindings.key.count; i++) xassert(conf->bindings.key.arr[i].action != BIND_ACTION_NONE); @@ -3107,19 +3183,9 @@ config_clone(const struct config *old) spawn_template_clone(&conf->bell.command, &old->bell.command); spawn_template_clone(&conf->notify, &old->notify); - for (size_t i = 0; i < ALEN(conf->fonts); i++) { - struct config_font_list *dst = &conf->fonts[i]; - const struct config_font_list *src = &old->fonts[i]; - - dst->count = src->count; - dst->arr = xmalloc(dst->count * sizeof(dst->arr[0])); - - for (size_t j = 0; j < dst->count; j++) { - dst->arr[j].pt_size = src->arr[j].pt_size; - dst->arr[j].px_size = src->arr[j].px_size; - dst->arr[j].pattern = xstrdup(src->arr[j].pattern); - } - } + for (size_t i = 0; i < ALEN(conf->fonts); i++) + config_font_list_clone(&conf->fonts[i], &old->fonts[i]); + config_font_list_clone(&conf->csd.font, &old->csd.font); conf->url.label_letters = xwcsdup(old->url.label_letters); spawn_template_clone(&conf->url.launch, &old->url.launch); @@ -3182,6 +3248,8 @@ config_free(struct config conf) config_font_list_destroy(&conf.fonts[i]); free(conf.server_socket_path); + config_font_list_destroy(&conf.csd.font); + free(conf.url.label_letters); spawn_template_free(&conf.url.launch); for (size_t i = 0; i < conf.url.prot_count; i++) diff --git a/config.h b/config.h index 729eba3e..ae7ff79a 100644 --- a/config.h +++ b/config.h @@ -228,6 +228,8 @@ struct config { uint32_t maximize; uint32_t close; } color; + + struct config_font_list font; } csd; size_t render_worker_count; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 8b2f1dc8..4a4be533 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -518,6 +518,12 @@ component. Titlebar AARRGGBB color. Default: use the default _foreground_ color. +*font* + Font to use for the title bar. This is a list of fonts, similar to + the main *font* option. Note that the font will be sized using the + title bar size. That is, all *:size* and *:pixelsize* attributes + will be ignored. Default: _primary font_. + *button-width* Width, in pixels (subject to output scaling), of the minimize/maximize/close buttons. Default: _26_. diff --git a/foot.ini b/foot.ini index 31167cd5..38f15b2e 100644 --- a/foot.ini +++ b/foot.ini @@ -94,6 +94,7 @@ [csd] # preferred=server # size=26 +# font= # color= # button-width=26 # button-color= diff --git a/render.c b/render.c index 6e0e82bf..f42279c9 100644 --- a/render.c +++ b/render.c @@ -246,9 +246,10 @@ color_hex_to_pixman(uint32_t color) static inline uint32_t color_dim(uint32_t color) { + uint32_t alpha = color & 0xff000000; int hue, sat, lum; rgb_to_hsl(color, &hue, &sat, &lum); - return hsl_to_rgb(hue, sat, lum / 1.5); + return alpha | hsl_to_rgb(hue, sat, lum / 1.5); } static inline uint32_t @@ -1575,32 +1576,144 @@ render_csd_part(struct terminal *term, pixman_image_unref(src); } +static void +render_osd(struct terminal *term, + struct wl_surface *surf, struct wl_subsurface *sub_surf, + struct fcft_font *font, struct buffer *buf, + const wchar_t *text, uint32_t _fg, uint32_t _bg, + unsigned x, unsigned y) +{ + pixman_region32_t clip; + pixman_region32_init_rect(&clip, 0, 0, buf->width, buf->height); + pixman_image_set_clip_region32(buf->pix[0], &clip); + pixman_region32_fini(&clip); + + uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8); + pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &bg, 1, + &(pixman_rectangle16_t){0, 0, buf->width, buf->height}); + + pixman_color_t fg = color_hex_to_pixman(_fg); + const int x_ofs = term->font_x_ofs; + + const size_t len = wcslen(text); + struct fcft_text_run *text_run = NULL; + const struct fcft_glyph **glyphs = NULL; + const struct fcft_glyph *_glyphs[len]; + size_t glyph_count = 0; + + if (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) { + text_run = fcft_text_run_rasterize(font, len, text, term->font_subpixel); + + if (text_run != NULL) { + glyphs = text_run->glyphs; + glyph_count = text_run->count; + } + } + + if (glyphs == NULL) { + for (size_t i = 0; i < len; i++) { + const struct fcft_glyph *glyph = fcft_glyph_rasterize( + font, text[i], term->font_subpixel); + + if (glyph == NULL) + continue; + + _glyphs[glyph_count++] = glyph; + } + + glyphs = _glyphs; + } + + pixman_image_t *src = pixman_image_create_solid_fill(&fg); + + for (size_t i = 0; i < glyph_count; i++) { + const struct fcft_glyph *glyph = glyphs[i]; + + if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) { + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0, + x + x_ofs + glyph->x, y + term->font_y_ofs + font->ascent - glyph->y, + glyph->width, glyph->height); + } else { + pixman_image_composite32( + PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0, + x + x_ofs + glyph->x, y + term->font_y_ofs + font->ascent - glyph->y, + glyph->width, glyph->height); + } + + x += glyph->advance.x; + } + + fcft_text_run_destroy(text_run); + pixman_image_unref(src); + pixman_image_set_clip_region32(buf->pix[0], NULL); + + xassert(buf->width % term->scale == 0); + xassert(buf->height % term->scale == 0); + + quirk_weston_subsurface_desync_on(sub_surf); + wl_surface_attach(surf, buf->wl_buf, 0, 0); + wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); + wl_surface_set_buffer_scale(surf, term->scale); + + struct wl_region *region = wl_compositor_create_region(term->wl->compositor); + if (region != NULL) { + wl_region_add(region, 0, 0, buf->width, buf->height); + wl_surface_set_opaque_region(surf, region); + wl_region_destroy(region); + } + + wl_surface_commit(surf); + quirk_weston_subsurface_desync_off(sub_surf); +} + static void render_csd_title(struct terminal *term, const struct csd_data *info, struct buffer *buf) { xassert(term->window->csd_mode == CSD_YES); - struct wl_surface *surf = term->window->csd.surface[CSD_SURF_TITLE].surf; + struct wl_surf_subsurf *surf = &term->window->csd.surface[CSD_SURF_TITLE]; xassert(info->width > 0 && info->height > 0); xassert(info->width % term->scale == 0); xassert(info->height % term->scale == 0); - uint32_t _color = term->conf->colors.fg; - uint16_t alpha = 0xffff; + uint32_t bg = term->conf->csd.color.title_set + ? term->conf->csd.color.title + : 0xffu << 24 | term->conf->colors.fg; + uint32_t fg = term->conf->csd.color.buttons_set + ? term->conf->csd.color.buttons + : term->conf->colors.bg; - if (term->conf->csd.color.title_set) { - _color = term->conf->csd.color.title; - alpha = _color >> 24 | (_color >> 24 << 8); + if (!term->visual_focus) { + bg = color_dim(bg); + fg = color_dim(fg); } - if (!term->visual_focus) - _color = color_dim(_color); + const wchar_t *title_text = L""; + wchar_t *_title_text = NULL; - pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); - render_csd_part(term, surf, buf, info->width, info->height, &color); - csd_commit(term, surf, buf); + int chars = mbstowcs(NULL, term->window_title, 0); + if (chars >= 0) { + _title_text = xmalloc((chars + 1) * sizeof(wchar_t)); + mbstowcs(_title_text, term->window_title, chars + 1); + title_text = _title_text; + } + + struct wl_window *win = term->window; + const int margin = win->csd.font->space_advance.x > 0 + ? win->csd.font->space_advance.x + : win->csd.font->max_advance.x; + + render_osd(term, surf->surf, surf->sub, win->csd.font, + buf, title_text, fg, bg, margin, + (buf->height - win->csd.font->height) / 2); + + csd_commit(term, surf->surf, buf); + free(_title_text); } static void @@ -1912,59 +2025,6 @@ render_csd(struct terminal *term) render_csd_title(term, &infos[CSD_SURF_TITLE], bufs[CSD_SURF_TITLE]); } -static void -render_osd(struct terminal *term, - struct wl_surface *surf, struct wl_subsurface *sub_surf, - struct buffer *buf, - const wchar_t *text, uint32_t _fg, uint32_t _bg, - unsigned x, unsigned y) -{ - pixman_color_t bg = color_hex_to_pixman(_bg); - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix[0], &bg, 1, - &(pixman_rectangle16_t){0, 0, buf->width, buf->height}); - - struct fcft_font *font = term->fonts[0]; - pixman_color_t fg = color_hex_to_pixman(_fg); - - const int x_ofs = term->font_x_ofs; - - for (size_t i = 0; i < wcslen(text); i++) { - const struct fcft_glyph *glyph = fcft_glyph_rasterize( - font, text[i], term->font_subpixel); - - if (glyph == NULL) - continue; - - pixman_image_t *src = pixman_image_create_solid_fill(&fg); - pixman_image_composite32( - PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0, - x + x_ofs + glyph->x, y + font_baseline(term) - glyph->y, - glyph->width, glyph->height); - pixman_image_unref(src); - - x += term->cell_width; - } - - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); - - quirk_weston_subsurface_desync_on(sub_surf); - wl_surface_attach(surf, buf->wl_buf, 0, 0); - wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); - wl_surface_set_buffer_scale(surf, term->scale); - - struct wl_region *region = wl_compositor_create_region(term->wl->compositor); - if (region != NULL) { - wl_region_add(region, 0, 0, buf->width, buf->height); - wl_surface_set_opaque_region(surf, region); - wl_region_destroy(region); - } - - wl_surface_commit(surf); - quirk_weston_subsurface_desync_off(sub_surf); -} - static void render_scrollback_position(struct terminal *term) { @@ -2093,8 +2153,8 @@ render_scrollback_position(struct terminal *term) term, win->scrollback_indicator.surf, win->scrollback_indicator.sub, - buf, text, - term->colors.table[0], term->colors.table[8 + 4], + term->fonts[0], buf, text, + term->colors.table[0], 0xffu << 24 | term->colors.table[8 + 4], width - margin - wcslen(text) * term->cell_width, margin); } @@ -2127,8 +2187,8 @@ render_render_timer(struct terminal *term, struct timeval render_time) term, win->render_timer.surf, win->render_timer.sub, - buf, text, - term->colors.table[0], term->colors.table[8 + 1], + term->fonts[0], buf, text, + term->colors.table[0], 0xffu << 24 | term->colors.table[8 + 1], margin, margin); } @@ -2630,7 +2690,6 @@ render_search_box(struct terminal *term) pixman_image_set_clip_region32(buf->pix[0], &clip); pixman_region32_fini(&clip); - #define WINDOW_X(x) (margin + x) #define WINDOW_Y(y) (term->height - margin - height + y) @@ -3097,7 +3156,8 @@ render_urls(struct terminal *term) (term->margins.top + y) / term->scale); render_osd( - term, surf, sub_surf, bufs[i], label, fg, bg, x_margin, y_margin); + term, surf, sub_surf, term->fonts[0], bufs[i], label, + fg, 0xffu << 24 | bg, x_margin, y_margin); free(info[i].text); } @@ -3667,6 +3727,8 @@ render_refresh_title(struct terminal *term) term->render.title.last_update = now; render_update_title(term); } + + render_refresh_csd(term); } void diff --git a/wayland.c b/wayland.c index 0679dee3..a83ef59d 100644 --- a/wayland.c +++ b/wayland.c @@ -30,6 +30,36 @@ #include "util.h" #include "xmalloc.h" +static void +csd_reload_font(struct wl_window *win, int old_scale) +{ + struct terminal *term = win->term; + const struct config *conf = term->conf; + + const int scale = term->scale; + + bool enable_csd = win->csd_mode == CSD_YES && !win->is_fullscreen; + if (!enable_csd) + return; + if (win->csd.font != NULL && scale == old_scale) + return; + + fcft_destroy(win->csd.font); + + const char *patterns[conf->csd.font.count]; + for (size_t i = 0; i < conf->csd.font.count; i++) + patterns[i] = conf->csd.font.arr[i].pattern; + + char pixelsize[32]; + snprintf(pixelsize, sizeof(pixelsize), + "pixelsize=%u", conf->csd.title_height * scale * 1 / 2); + + LOG_DBG("loading CSD font \"%s:%s\" (old-scale=%d, scale=%d)", + patterns[0], pixelsize, old_scale, scale); + + win->csd.font = fcft_from_name(conf->csd.font.count, patterns, pixelsize); +} + static void csd_instantiate(struct wl_window *win) { @@ -46,6 +76,8 @@ csd_instantiate(struct wl_window *win) win, win->csd.surface[CSD_SURF_TITLE].surf, &win->csd.surface[i]); xassert(ret); } + + csd_reload_font(win, -1); } static void @@ -53,6 +85,9 @@ csd_destroy(struct wl_window *win) { struct terminal *term = win->term; + fcft_destroy(term->window->csd.font); + term->window->csd.font = NULL; + for (size_t i = 0; i < ALEN(win->csd.surface); i++) wayl_win_subsurface_destroy(&win->csd.surface[i]); shm_purge(term->render.chains.csd); @@ -294,6 +329,7 @@ update_term_for_output_change(struct terminal *term) render_resize(term, term->width / term->scale, term->height / term->scale); term_font_dpi_changed(term, old_scale); term_font_subpixel_changed(term); + csd_reload_font(term->window, old_scale); } static void diff --git a/wayland.h b/wayland.h index f62819a4..bc044169 100644 --- a/wayland.h +++ b/wayland.h @@ -20,6 +20,7 @@ #include #endif +#include #include #include "fdm.h" @@ -402,6 +403,7 @@ struct wl_window { struct { struct wl_surf_subsurf surface[CSD_SURF_COUNT]; + struct fcft_font *font; int move_timeout_fd; uint32_t serial; } csd;