From a3971feca8c918f629c45f24b9841c2b403f7ee4 Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Fri, 10 Apr 2026 14:52:24 +0200 Subject: [PATCH] menu: implement menu accelerators Menu accelerators are one-letter mnemonics to quickly select/exec items from the current menu. For each menu item, the accelerator is defined as the first character of the item label, converted to lowercase. A different accelerator can be explicitly defined in menu.xml with the special '_' character before the target letter. - Add a field `accelerator` to the `menuitem` struct - Implement `menu_item_select_by_accelerator()` Example: The accelerator for an item with the label "e_macs" is 'm'. --- include/menu/menu.h | 16 ++++++++++++- src/menu/menu.c | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/include/menu/menu.h b/include/menu/menu.h index 46bbca12..736e32b1 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -23,6 +23,7 @@ struct menuitem { char *text; char *icon_name; const char *arrow; + char accelerator; struct menu *parent; struct menu *submenu; bool selectable; @@ -66,6 +67,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(char accelerator); + void menu_submenu_enter(void); void menu_submenu_leave(void); bool menu_call_selected_actions(void); @@ -100,7 +114,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/src/menu/menu.c b/src/menu/menu.c index 251f9a30..c1169a2a 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -2,6 +2,7 @@ #define _POSIX_C_SOURCE 200809L #include "menu/menu.h" #include +#include #include #include #include @@ -143,6 +144,16 @@ item_create(struct menu *menu, const char *text, const char *icon_name, bool sho menuitem->text = xstrdup(text); menuitem->arrow = show_arrow ? "›" : NULL; + const char *it = text; + menuitem->accelerator = tolower(*it); + while (*it != '\0') { + if (*it == '_' && *it != '\0') { + menuitem->accelerator = *(++it); + break; + } + it++; + } + #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { menuitem->icon_name = xstrdup(icon_name); @@ -1460,6 +1471,50 @@ menu_item_select_previous(void) menu_item_select(/* forward */ false); } +bool +menu_item_select_by_accelerator(char accelerator) +{ + struct menu *menu = get_selection_leaf(); + if (!menu) { + return false; + } + + bool needs_exec = true; + 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; + item = wl_container_of(current, item, link); + if (!matched && item->accelerator == accelerator) { + /* Menuentry with a matching accelerator found */ + next_selection = item; + matched = true; + } else if (matched && item->accelerator == accelerator) { + /* Another menuentry with such accelerator found, + 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) { + /* Submenu was opened, select the first menuitem + without executing */ + needs_exec = false; + menu_submenu_enter(); + } + } + return needs_exec; +} + + bool menu_call_selected_actions(void) {