From 0caefa6a9e42628549fc81b8af154bedcfc51163 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 5 May 2026 10:28:52 +0200 Subject: [PATCH 1/4] Translation updates from weblate Co-authored-by: Tobias Si Co-authored-by: Weblate Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/sr_Latn/ Translation: Labwc/labwc --- po/LINGUAS | 2 +- po/sr_Latn.po | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 po/sr_Latn.po diff --git a/po/LINGUAS b/po/LINGUAS index 70114b72..50e5157a 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1 +1 @@ -ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW +ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sr_Latn sv tr uk vi zh_CN zh_TW diff --git a/po/sr_Latn.po b/po/sr_Latn.po new file mode 100644 index 00000000..ef41f62e --- /dev/null +++ b/po/sr_Latn.po @@ -0,0 +1,81 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-09-19 21:09+1000\n" +"PO-Revision-Date: 2026-05-05 08:28+0000\n" +"Last-Translator: Tobias Si \n" +"Language-Team: Serbian (latin) \n" +"Language: sr_Latn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:1016 +msgid "Go there..." +msgstr "Idi tamo..." + +#: src/menu/menu.c:1034 +msgid "Terminal" +msgstr "Terminal" + +#: src/menu/menu.c:1040 +msgid "Reconfigure" +msgstr "Podesi" + +#: src/menu/menu.c:1042 +msgid "Exit" +msgstr "Izlaz" + +#: src/menu/menu.c:1056 +msgid "Minimize" +msgstr "Minimiziraj" + +#: src/menu/menu.c:1058 +msgid "Maximize" +msgstr "Maksimiziraj" + +#: src/menu/menu.c:1060 +msgid "Fullscreen" +msgstr "Prikaz preko celog ekrana" + +#: src/menu/menu.c:1062 +msgid "Roll Up/Down" +msgstr "Prevuci Gore/Dole" + +#: src/menu/menu.c:1064 +msgid "Decorations" +msgstr "Dekoracije" + +#: src/menu/menu.c:1066 +msgid "Always on Top" +msgstr "Uvek na vrhu" + +#: src/menu/menu.c:1071 +msgid "Move Left" +msgstr "Pomeri levo" + +#: src/menu/menu.c:1078 +msgid "Move Right" +msgstr "Pomeri desno" + +#: src/menu/menu.c:1083 +msgid "Always on Visible Workspace" +msgstr "Uvek na vidljivom radnom prostoru" + +#: src/menu/menu.c:1086 +msgid "Workspace" +msgstr "Radni prostor" + +#: src/menu/menu.c:1089 +msgid "Close" +msgstr "Zatvori" From 07a0a4e59b02b7a0679c938080d6bb35b9ad3183 Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Sun, 26 Apr 2026 12:33:54 +0200 Subject: [PATCH 2/4] scaled-buffer: introduce `scaled_font_buffer_update_markup()` This function behaves identically to `scaled_font_buffer_update()` but allows setting the text as pango markup, supporting further customization like underscores. --- include/common/font.h | 4 +++- include/scaled-buffer/scaled-font-buffer.h | 15 +++++++++++-- src/common/font.c | 10 +++++++-- src/scaled-buffer/scaled-font-buffer.c | 26 ++++++++++++++++++---- 4 files changed, 46 insertions(+), 9 deletions(-) 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/scaled-buffer/scaled-font-buffer.h b/include/scaled-buffer/scaled-font-buffer.h index a5e95087..2f0fe422 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]; @@ -69,8 +70,18 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, * bg_color is ignored for font buffers created with * scaled_font_buffer_create_for_titlebar(). */ -void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, - int max_width, struct font *font, const float *color, +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); +/** + * Update an existing auto scaling font buffer allowing the use of pango markup. + * + * Behaves identically to scaled_font_buffer_update(), but allows customization + * of the `use_markup` field of the @self struct via @use_markup. + */ +void scaled_font_buffer_update_markup(struct scaled_font_buffer *self, + const char *text, int max_width, struct font *font, const float *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/scaled-buffer/scaled-font-buffer.c b/src/scaled-buffer/scaled-font-buffer.c index bb93fc67..c748c00c 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 @@ -104,10 +105,10 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, return self; } -void -scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, +static 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); @@ -139,3 +141,19 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, scaled_buffer_request_update(self->scaled_buffer, self->width, self->height); } + +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) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, false); +} + +void +scaled_font_buffer_update_markup(struct scaled_font_buffer *self, const char *text, + int max_width, struct font *font, const float *color, + const float *bg_color, bool use_markup) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, use_markup); +} From 3632c6703a3fcd4545632ed62a1d635a92a60077 Mon Sep 17 00:00:00 2001 From: Alex Chernika Date: Fri, 10 Apr 2026 14:52:24 +0200 Subject: [PATCH 3/4] 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'. --- docs/labwc-menu.5.scd | 15 +++++ include/menu/menu.h | 17 ++++- src/input/keyboard.c | 17 +++-- src/menu/menu.c | 149 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 190 insertions(+), 8 deletions(-) diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd index cf7ccbc7..df04ccf9 100644 --- a/docs/labwc-menu.5.scd +++ b/docs/labwc-menu.5.scd @@ -148,6 +148,21 @@ obmenu-generator with the menu generator of your choice): ``` +# ACCELERATORS / MNEMONICS + +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. Accelerators can be any unicode character and are not limited to +ASCII. A usual underscore can be shown by duplicating it. + +If the menu only contains a single instance of the pressed accelerator the item +will be executed directly. Otherwise, all matching items are cycled through. + +Example: +The accelerator for an item with the label "e_Macs" is 'm'. + # LOCALISATION Available localisation for the default "client-menu" is only shown if no 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/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..7b132806 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -2,12 +2,16 @@ #define _POSIX_C_SOURCE 200809L #include "menu/menu.h" #include +#include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -129,6 +133,91 @@ validate(void) } } +static uint32_t +get_unicode_char(const char *first_byte, size_t *out_bytes) +{ + if (!first_byte || first_byte[0] == '\0') { + *out_bytes = 0; + return 0; + } + + /* Temporarily set locale to UTF-8 */ + locale_t utf8_locale = newlocale(LC_CTYPE_MASK, "C.UTF-8", (locale_t)0); + locale_t old_locale = (locale_t)0; + if (utf8_locale != (locale_t)0) { + old_locale = uselocale(utf8_locale); + } + + uint32_t result = 0; + char32_t codepoint = 0; + mbstate_t state = {0}; + size_t bytes = mbrtoc32(&codepoint, first_byte, 4, &state); + if (bytes > 0 && bytes <= 4) { + *out_bytes = bytes; + result = (uint32_t)towlower((wint_t)codepoint); + } else { + *out_bytes = 1; + result = (uint32_t)(unsigned char)first_byte[0]; + } + + /* Restore previous locale */ + if (utf8_locale != (locale_t)0) { + uselocale(old_locale); + freelocale(utf8_locale); + } + + return result; +} + +static void +item_parse_accelerator(struct menuitem *item, const char *text) +{ + const char *accel_ptr = NULL; + char *underscore = strchr(text, '_'); + while (underscore) { + if (underscore[1] == '_') { + /* Ignore escaped underscores */ + underscore = strchr(underscore + 2, '_'); + } else if (underscore[1] != '\0') { + /* Found a valid accelerator */ + accel_ptr = underscore + 1; + break; + } else { + /* Ignore empty accelertor */ + break; + } + } + + size_t bytes = 0; + if (!accel_ptr) { + item->text = xstrdup(text); + item->accelerator = get_unicode_char(text, &bytes); + } else { + item->use_markup = true; + item->accelerator = get_unicode_char(accel_ptr, &bytes); + item->text = strdup_printf("%.*s%.*s%s", + /* Prefix length + prefix */ + (int)(accel_ptr - 1 - text), text, + /* Accelerator (utf-8 byte) length + accelerator */ + (int)bytes, accel_ptr, + /* Remainder */ + accel_ptr + bytes); + } + + /* Remove undescores used for escaping */ + char *src = item->text; + char *dst = item->text; + while (*src) { + if (*src == '_' && *(src + 1) == '_') { + *dst++ = '_'; + src += 2; + } else { + *dst++ = *src++; + } + } + *dst = '\0'; +} + static struct menuitem * item_create(struct menu *menu, const char *text, const char *icon_name, bool show_arrow) { @@ -140,8 +229,8 @@ 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; + item_parse_accelerator(menuitem, text); #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { @@ -212,8 +301,8 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, /* Create label */ 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); + scaled_font_buffer_update_markup(label_buffer, item->text, label_max_width, + &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; @@ -1460,6 +1549,60 @@ 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) { + return false; + } + + 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) { From f42e1895d41187cfca00e9cd61aeaeabbb830fbf Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Tue, 5 May 2026 19:11:34 +0100 Subject: [PATCH 4/4] buf.c: fix -fanalyze warning ../src/common/buf.c:61:28: error: write to string literal [-Werror=analyzer-write-to-string-literal] 61 | *p = '\0'; | ~~~^~~~~~ --- src/common/buf.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/common/buf.c b/src/common/buf.c index c141b62e..2a68b734 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -53,15 +53,14 @@ buf_expand_shell_variables(struct buf *s) if (s->data[i] == '$' && isvalid(s->data[i+1])) { /* expand environment variable */ buf_clear(&environment_variable); - buf_add(&environment_variable, s->data + i + 1); - char *p = environment_variable.data; - while (isvalid(*p)) { - ++p; + int len = 0; + while (isvalid(s->data[i + 1 + len])) { + buf_add_char(&environment_variable, s->data[i + 1 + len]); + ++len; } - *p = '\0'; - i += strlen(environment_variable.data); + i += len; strip_curly_braces(environment_variable.data); - p = getenv(environment_variable.data); + char *p = getenv(environment_variable.data); if (p) { buf_add(&tmp, p); }