diff --git a/include/common/font.h b/include/common/font.h index a7a5ba55..22be2ae8 100644 --- a/include/common/font.h +++ b/include/common/font.h @@ -4,6 +4,7 @@ #include #include +#include struct lab_data_buffer; @@ -43,10 +44,11 @@ void font_get_buffer_size(int max_width, const char *text, struct font *font, * @font: font description * @color: foreground color in rgba format * @bg_pattern: background pattern + * @use_markup: flag to render pango markup */ void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale); + cairo_pattern_t *bg_pattern, double scale, bool use_markup); /** * font_finish - free some font related resources diff --git a/include/menu/menu.h b/include/menu/menu.h index 46bbca12..3d546efa 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -23,9 +23,11 @@ struct menuitem { char *text; char *icon_name; const char *arrow; + uint32_t accelerator; struct menu *parent; struct menu *submenu; bool selectable; + bool use_markup; enum menuitem_type type; int native_width; struct wlr_scene_tree *tree; @@ -66,6 +68,19 @@ struct menu { /* For keyboard support */ void menu_item_select_next(void); void menu_item_select_previous(void); + +/** + * menu_item_select_by_accelerator - selects the next menu item with + * a matching accelerator, starting after the current selection + * + * @accelerator a shortcut to quickly select/open an item, defined in menu.xml + * with an underscore in the item label before the target letter. + * + * Return: a boolean value that represents whether the newly selected item + * needs to be executed. + */ +bool menu_item_select_by_accelerator(uint32_t accelerator); + void menu_submenu_enter(void); void menu_submenu_leave(void); bool menu_call_selected_actions(void); @@ -100,7 +115,7 @@ void menu_open_root(struct menu *menu, int x, int y); void menu_process_cursor_motion(struct wlr_scene_node *node); /** - * menu_close_root- close root menu + * menu_close_root - close root menu * * This function will close server.menu_current and set it to NULL. * Asserts that server.input_mode is set to LAB_INPUT_STATE_MENU. diff --git a/include/scaled-buffer/scaled-font-buffer.h b/include/scaled-buffer/scaled-font-buffer.h index a5e95087..a76fe03f 100644 --- a/include/scaled-buffer/scaled-font-buffer.h +++ b/include/scaled-buffer/scaled-font-buffer.h @@ -15,6 +15,7 @@ struct scaled_font_buffer { /* Private */ char *text; + bool use_markup; int max_width; float color[4]; float bg_color[4]; @@ -71,6 +72,6 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, */ void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, const float *color, - const float *bg_color); + const float *bg_color, bool use_markup); #endif /* LABWC_SCALED_FONT_BUFFER_H */ diff --git a/src/common/font.c b/src/common/font.c index b307729c..5162d284 100644 --- a/src/common/font.c +++ b/src/common/font.c @@ -79,7 +79,7 @@ font_get_buffer_size(int max_width, const char *text, struct font *font, void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale) + cairo_pattern_t *bg_pattern, double scale, bool use_markup) { if (string_null_or_empty(text)) { return; @@ -123,7 +123,13 @@ font_buffer_create(struct lab_data_buffer **buffer, int max_width, PangoLayout *layout = pango_cairo_create_layout(cairo); pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false); pango_layout_set_width(layout, width * PANGO_SCALE); - pango_layout_set_text(layout, text, -1); + + if (use_markup) { + pango_layout_set_markup(layout, text, -1); + } else { + pango_layout_set_text(layout, text, -1); + } + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); if (!opaque_bg) { diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 7e5abfb6..685b58b8 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -58,8 +58,8 @@ create_fields_scene(struct view *view, struct scaled_font_buffer *font_buffer = scaled_font_buffer_create(parent); scaled_font_buffer_update(font_buffer, - buf.data, field_width, - &rc.font_osd, text_color, bg_color); + buf.data, field_width, &rc.font_osd, + text_color, bg_color, false); node = &font_buffer->scene_buffer->node; height = font_height(&rc.font_osd); } @@ -143,7 +143,7 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output) wlr_scene_node_set_position(&font_buffer->scene_buffer->node, x, y + (switcher_theme->item_height - font_height(&font)) / 2); scaled_font_buffer_update(font_buffer, workspace_name, 0, - &font, text_color, bg_color); + &font, text_color, bg_color, false); y += switcher_theme->item_height; } diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index defedf09..7cd7ff9f 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -116,7 +116,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view, scaled_font_buffer_create(parent); scaled_font_buffer_update(buffer, buf.data, switcher_theme->item_width - 2 * switcher_theme->item_padding, - &rc.font_osd, text_color, bg_color); + &rc.font_osd, text_color, bg_color, false); buf_reset(&buf); wlr_scene_node_set_position(&buffer->scene_buffer->node, (switcher_theme->item_width - buffer->width) / 2, y); diff --git a/src/input/key-state.c b/src/input/key-state.c index 6d089891..ea8897a6 100644 --- a/src/input/key-state.c +++ b/src/input/key-state.c @@ -116,7 +116,7 @@ update_key_indicator_callback(void *data) buf_add_fmt(&buf, "%s (%d), ", keyname, pressed.values[i]); } scaled_font_buffer_update(indicator_state.sfb_pressed, buf.data, - -1, &rc.font_osd, black, white); + -1, &rc.font_osd, black, white, false); buf_clear(&buf); buf_add(&buf, "bound="); @@ -126,7 +126,7 @@ update_key_indicator_callback(void *data) buf_add_fmt(&buf, "%s (%d), ", keyname, bound.values[i]); } scaled_font_buffer_update(indicator_state.sfb_bound, buf.data, - -1, &rc.font_osd, black, white); + -1, &rc.font_osd, black, white, false); buf_clear(&buf); buf_add(&buf, "pressed_sent="); @@ -136,7 +136,7 @@ update_key_indicator_callback(void *data) buf_add_fmt(&buf, "%s (%d), ", keyname, pressed_sent.values[i]); } scaled_font_buffer_update(indicator_state.sfb_pressed_sent, buf.data, -1, - &rc.font_osd, black, white); + &rc.font_osd, black, white, false); buf_clear(&buf); buf_add(&buf, "modifiers="); @@ -148,7 +148,7 @@ update_key_indicator_callback(void *data) } buf_add_fmt(&buf, "(%d)", all_modifiers); scaled_font_buffer_update(indicator_state.sfb_modifiers, buf.data, -1, - &rc.font_osd, black, white); + &rc.font_osd, black, white, false); buf_reset(&buf); } diff --git a/src/input/keyboard.c b/src/input/keyboard.c index 98be5d11..36884144 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -444,15 +444,24 @@ handle_menu_keys(struct keysyms *syms) break; case XKB_KEY_Return: case XKB_KEY_KP_Enter: - menu_call_selected_actions(); + if (!menu_call_selected_actions()) { + menu_submenu_enter(); + }; break; case XKB_KEY_Escape: menu_close_root(); cursor_update_focus(); break; - default: - continue; - } + default: { + uint32_t accelerator = xkb_keysym_to_utf32(syms->syms[i]); + if (accelerator == 0) { + continue; + } + if (menu_item_select_by_accelerator(accelerator)) { + menu_call_selected_actions(); + } + break; + }} break; } } diff --git a/src/menu/menu.c b/src/menu/menu.c index 251f9a30..310cd736 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -2,12 +2,15 @@ #define _POSIX_C_SOURCE 200809L #include "menu/menu.h" #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -130,7 +133,7 @@ validate(void) } static struct menuitem * -item_create(struct menu *menu, const char *text, const char *icon_name, bool show_arrow) +item_create(struct menu *menu, char *text, const char *icon_name, bool show_arrow) { assert(menu); assert(text); @@ -140,9 +143,52 @@ item_create(struct menu *menu, const char *text, const char *icon_name, bool sho menuitem->parent = menu; menuitem->selectable = true; menuitem->type = LAB_MENU_ITEM; - menuitem->text = xstrdup(text); menuitem->arrow = show_arrow ? "›" : NULL; + uint32_t accelerator = 0; + char *new_text = NULL; + char *it = text; + while (*it != '\0') { + if (*it == '_') { + char32_t codepoint = 0; + mbstate_t state = {0}; + size_t bytes = mbrtoc32(&codepoint, it + 1, + MB_CUR_MAX, &state); + if (bytes > 0 && bytes <= 4) { + accelerator = (uint32_t)towlower((wint_t)codepoint); + } + + if (*(it + 1) != '\0') { + int underscore_index = it - text; + new_text = strdup_printf("%.*s%.*s%s", + underscore_index, text, (int)bytes, + it + 1, it + 1 + bytes); + break; + } + } + it++; + } + + /* Fallback to the first character of the label */ + if (accelerator == 0 && text[0] != '\0') { + char32_t codepoint = 0; + mbstate_t state = {0}; + + size_t bytes = mbrtoc32(&codepoint, text, MB_CUR_MAX, &state); + if (bytes > 0 && bytes <= 4) { + accelerator = (uint32_t)towlower((wint_t)codepoint); + } + } + + menuitem->accelerator = accelerator; + if (new_text) { + menuitem->text = new_text; + menuitem->use_markup = true; + } else { + menuitem->text = xstrdup(text); + menuitem->use_markup = false; + } + #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { menuitem->icon_name = xstrdup(icon_name); @@ -213,7 +259,7 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, struct scaled_font_buffer *label_buffer = scaled_font_buffer_create(tree); assert(label_buffer); scaled_font_buffer_update(label_buffer, item->text, label_max_width, - &rc.font_menuitem, text_color, bg_color); + &rc.font_menuitem, text_color, bg_color, item->use_markup); /* Vertically center and left-align label */ int x = theme->menu_items_padding_x + icon_width; int y = (theme->menu_item_height - label_buffer->height) / 2; @@ -227,7 +273,7 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, struct scaled_font_buffer *arrow_buffer = scaled_font_buffer_create(tree); assert(arrow_buffer); scaled_font_buffer_update(arrow_buffer, item->arrow, -1, - &rc.font_menuitem, text_color, bg_color); + &rc.font_menuitem, text_color, bg_color, false); /* Vertically center and right-align arrow */ x += label_max_width + theme->menu_items_padding_x; y = (theme->menu_item_height - label_buffer->height) / 2; @@ -366,7 +412,7 @@ title_create_scene(struct menuitem *menuitem, int *item_y) scaled_font_buffer_create(menuitem->normal_tree); assert(title_font_buffer); scaled_font_buffer_update(title_font_buffer, menuitem->text, - text_width, &rc.font_menuheader, text_color, bg_color); + text_width, &rc.font_menuheader, text_color, bg_color, false); int title_x = 0; switch (theme->menu_title_text_justify) { @@ -1460,6 +1506,57 @@ menu_item_select_previous(void) menu_item_select(/* forward */ false); } +bool +menu_item_select_by_accelerator(uint32_t accelerator) +{ + struct menu *menu = get_selection_leaf(); + if (!menu || wl_list_empty(&menu->menuitems)) { + return false; + } + + bool needs_exec = false; + bool matched = false; + + struct menuitem *selection = menu->selection.item; + struct wl_list *start = selection ? &selection->link : &menu->menuitems; + struct wl_list *current = start; + struct menuitem *item = NULL; + struct menuitem *next_selection = NULL; + do { + current = current->next; + if (current == &menu->menuitems) { + /* Allow wrap around */ + continue; + } + item = wl_container_of(current, item, link); + if (item->accelerator == accelerator) { + if (!matched) { + /* Found first match */ + next_selection = item; + needs_exec = true; + matched = true; + } else { + /* + * Found another match, + * cycle selection instead of executing + */ + needs_exec = false; + break; + } + } + } while (current != start); + + if (next_selection) { + menu_process_item_selection(next_selection); + if (needs_exec && next_selection->submenu) { + /* Since we can't execute a submenu, enter it. */ + needs_exec = false; + menu_submenu_enter(); + } + } + return needs_exec; +} + bool menu_call_selected_actions(void) { diff --git a/src/scaled-buffer/scaled-font-buffer.c b/src/scaled-buffer/scaled-font-buffer.c index bb93fc67..229d97d9 100644 --- a/src/scaled-buffer/scaled-font-buffer.c +++ b/src/scaled-buffer/scaled-font-buffer.c @@ -26,7 +26,7 @@ _create_buffer(struct scaled_buffer *scaled_buffer, double scale) /* Buffer gets free'd automatically along the backing wlr_buffer */ font_buffer_create(&buffer, self->max_width, self->height, self->text, - &self->font, self->color, bg_pattern, scale); + &self->font, self->color, bg_pattern, scale, self->use_markup); if (!buffer) { wlr_log(WLR_ERROR, "font_buffer_create() failed"); @@ -56,6 +56,7 @@ _equal(struct scaled_buffer *scaled_buffer_a, struct scaled_font_buffer *b = scaled_buffer_b->data; return str_equal(a->text, b->text) + && a->use_markup == b->use_markup && a->max_width == b->max_width && str_equal(a->font.name, b->font.name) && a->font.size == b->font.size @@ -107,7 +108,7 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, const float *color, - const float *bg_color) + const float *bg_color, bool use_markup) { assert(self); assert(text); @@ -120,6 +121,7 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, /* Update internal state */ self->text = xstrdup(text); + self->use_markup = use_markup; self->max_width = max_width; if (font->name) { self->font.name = xstrdup(font->name); diff --git a/src/ssd/resize-indicator.c b/src/ssd/resize-indicator.c index fa1166a3..45df44e1 100644 --- a/src/ssd/resize-indicator.c +++ b/src/ssd/resize-indicator.c @@ -202,7 +202,7 @@ resize_indicator_update(struct view *view) wlr_scene_node_set_position(&indicator->tree->node, x, y); scaled_font_buffer_update(indicator->text, text, width, &rc.font_osd, - rc.theme->osd_label_text_color, rc.theme->osd_bg_color); + rc.theme->osd_label_text_color, rc.theme->osd_bg_color, false); } void diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index d1a08810..816221db 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -473,7 +473,7 @@ ssd_update_title(struct ssd *ssd) const float bg_color[4] = {0, 0, 0, 0}; /* ignored */ scaled_font_buffer_update(subtree->title, view->title, title_bg_width, font, - text_color, bg_color); + text_color, bg_color, false); /* And finally update the cache */ dstate->width = subtree->title->width;