From 15d7c7bcc29e66f174c4de2420d371a9737ac6e4 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 4 May 2024 21:44:59 -0400 Subject: [PATCH 01/15] Revert "Remove wmenu -P flag" This reverts commit c05ab7520b452ee3b8bd974a18511dc370cbeabe. --- docs/wmenu.1.scd | 6 +++++- menu.c | 5 ++++- menu.h | 2 ++ render.c | 18 ++++++++++++++++-- wmenu.c | 4 +++- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 1ee623a..f489c84 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland # SYNOPSIS -*wmenu* [-biv] \ +*wmenu* [-biPv] \ [-f _font_] \ [-l _lines_] \ [-o _output_] \ @@ -35,6 +35,10 @@ $PATH and runs the result. *-i* wmenu matches menu items case insensitively. +*-P* + wmenu will not directly display the keyboard input, but instead replace it + with asterisks. All data from stdin will be ignored. + *-v* prints version information to stdout, then exits. diff --git a/menu.c b/menu.c index 3e8f9be..baa3061 100644 --- a/menu.c +++ b/menu.c @@ -89,7 +89,7 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n"; int opt; - while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) { + while ((opt = getopt(argc, argv, "bhiPvf:l:o:p:N:n:M:m:S:s:")) != -1) { switch (opt) { case 'b': menu->bottom = true; @@ -97,6 +97,9 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { case 'i': menu->strncmp = strncasecmp; break; + case 'P': + menu->passwd = true; + break; case 'v': puts("wmenu " VERSION); exit(EXIT_SUCCESS); diff --git a/menu.h b/menu.h index 0a6b22c..724aa53 100644 --- a/menu.h +++ b/menu.h @@ -30,6 +30,8 @@ struct menu { bool bottom; // The function used to match menu items int (*strncmp)(const char *, const char *, size_t); + // Whether the input is a password + bool passwd; // The font used to display the menu char *font; // The number of lines to list items vertically diff --git a/render.c b/render.c index e33898d..3813af5 100644 --- a/render.c +++ b/render.c @@ -79,8 +79,22 @@ static void render_prompt(struct menu *menu, cairo_t *cairo) { // Renders the input text. static void render_input(struct menu *menu, cairo_t *cairo) { - render_text(menu, cairo, menu->input, menu->promptw, 0, 0, - 0, menu->normalfg, menu->padding, menu->padding); + char *censort = NULL; + + if (menu->passwd) { + censort = calloc(1, sizeof(menu->input)); + if (!censort) { + return; + } + memset(censort, '*', strlen(menu->input)); + } + + render_text(menu, cairo, menu->passwd ? censort : menu->input, + menu->promptw, 0, 0, 0, menu->normalfg, menu->padding, menu->padding); + + if (censort) { + free(censort); + } } // Renders a cursor for the input field. diff --git a/wmenu.c b/wmenu.c index e7d37d9..0a3d0b0 100644 --- a/wmenu.c +++ b/wmenu.c @@ -19,7 +19,9 @@ static void read_items(struct menu *menu) { int main(int argc, char *argv[]) { struct menu *menu = menu_create(); menu_getopts(menu, argc, argv); - read_items(menu); + if (!menu->passwd) { + read_items(menu); + } int status = menu_run(menu); menu_destroy(menu); return status; From 30abca4f301a3e851d020119f654586c56c70263 Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 5 May 2024 10:13:01 -0400 Subject: [PATCH 02/15] Don't ignore stdin in password mode This makes password mode work for wmenu and wmenu-run without special cases. --- docs/wmenu.1.scd | 2 +- menu.c | 2 +- wmenu.c | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index f489c84..4519e8b 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -37,7 +37,7 @@ $PATH and runs the result. *-P* wmenu will not directly display the keyboard input, but instead replace it - with asterisks. All data from stdin will be ignored. + with asterisks. *-v* prints version information to stdout, then exits. diff --git a/menu.c b/menu.c index baa3061..f99bd54 100644 --- a/menu.c +++ b/menu.c @@ -85,7 +85,7 @@ static bool parse_color(const char *color, uint32_t *result) { // Parse menu options from command line arguments. void menu_getopts(struct menu *menu, int argc, char *argv[]) { const char *usage = - "Usage: wmenu [-biPvx] [-f font] [-l lines] [-o output] [-p prompt]\n" + "Usage: wmenu [-biPv] [-f font] [-l lines] [-o output] [-p prompt]\n" "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n"; int opt; diff --git a/wmenu.c b/wmenu.c index 0a3d0b0..e7d37d9 100644 --- a/wmenu.c +++ b/wmenu.c @@ -19,9 +19,7 @@ static void read_items(struct menu *menu) { int main(int argc, char *argv[]) { struct menu *menu = menu_create(); menu_getopts(menu, argc, argv); - if (!menu->passwd) { - read_items(menu); - } + read_items(menu); int status = menu_run(menu); menu_destroy(menu); return status; From 0fa9c359493b35ef0dda3b64f121fbe333f90f1a Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 25 May 2024 19:10:07 -0400 Subject: [PATCH 03/15] Update README.md --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index c4c7c63..84dba3e 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,3 @@ To use wmenu with Sway, you can add the following to your configuration file: set $menu wmenu-run bindsym $mod+d exec $menu ``` - -## Contributing - -Send patches and questions to [~adnano/wmenu-devel](https://lists.sr.ht/~adnano/wmenu-devel). - -Subscribe to release announcements on [~adnano/wmenu-announce](https://lists.sr.ht/~adnano/wmenu-announce). - -## Credits - -This project started as a fork of [dmenu-wl](https://github.com/nyyManni/dmenu-wayland). -However, most of the code was rewritten from scratch. From a0df7959f9182a87a833d0a7f653f5ac8a2b5d0e Mon Sep 17 00:00:00 2001 From: NAHTAIV3L Date: Sun, 2 Jun 2024 21:49:46 -0400 Subject: [PATCH 04/15] Make wmenu-run behave like dmenu_run --- wmenu-run.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wmenu-run.c b/wmenu-run.c index 228edc4..11e6699 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -39,7 +39,8 @@ static void activation_token_done(void *data, struct xdg_activation_token_v1 *ac menu_destroy(exe->menu); setenv("XDG_ACTIVATION_TOKEN", token, true); - execlp(exe->name, exe->name, NULL); + char* cmd[] = {"/bin/sh", "-c", exe->name, NULL}; + execvp(cmd[0], (char**)cmd); fprintf(stderr, "Failed to execute selection: %s\n", strerror(errno)); free(exe->name); @@ -52,13 +53,9 @@ static const struct xdg_activation_token_v1_listener activation_token_listener = }; static void exec(struct menu *menu) { - if (!menu->sel) { - return; - } - struct executable *exe = calloc(1, sizeof(struct executable)); exe->menu = menu; - exe->name = strdup(menu->sel->text); + exe->name = strdup(menu->input); struct xdg_activation_v1 *activation = context_get_xdg_activation(menu->context); struct xdg_activation_token_v1 *activation_token = xdg_activation_v1_get_activation_token(activation); From 7d717b3696e8295f1236bb5c6c69417f14394883 Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 9 Jun 2024 20:30:58 -0400 Subject: [PATCH 05/15] Streamline menu callbacks --- menu.c | 15 ++++----------- menu.h | 7 +++++-- wayland.c | 3 +-- wmenu-run.c | 42 ++++++++++++++++++++++-------------------- wmenu.c | 11 ++++++++++- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/menu.c b/menu.c index f99bd54..efd3e4a 100644 --- a/menu.c +++ b/menu.c @@ -22,7 +22,7 @@ #include "wayland.h" // Creates and returns a new menu. -struct menu *menu_create() { +struct menu *menu_create(menu_callback callback) { struct menu *menu = calloc(1, sizeof(struct menu)); menu->strncmp = strncmp; menu->font = "monospace 10"; @@ -32,6 +32,7 @@ struct menu *menu_create() { menu->promptfg = 0xeeeeeeff; menu->selectionbg = 0x005577ff; menu->selectionfg = 0xeeeeeeff; + menu->callback = callback; return menu; } @@ -571,18 +572,10 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_Return: case XKB_KEY_KP_Enter: if (shift) { - puts(menu->input); - fflush(stdout); - menu->exit = true; - } else if (menu->callback) { - menu->callback(menu); + menu->callback(menu, menu->input, true); } else { char *text = menu->sel ? menu->sel->text : menu->input; - puts(text); - fflush(stdout); - if (!ctrl) { - menu->exit = true; - } + menu->callback(menu, text, !ctrl); } break; case XKB_KEY_Left: diff --git a/menu.h b/menu.h index 724aa53..261c2f2 100644 --- a/menu.h +++ b/menu.h @@ -6,6 +6,9 @@ #include #include +struct menu; +typedef void (*menu_callback)(struct menu *menu, char *text, bool exit); + // A menu item. struct item { char *text; @@ -68,12 +71,12 @@ struct menu { struct item *sel; // selected item struct page *pages; // list of pages - void (*callback)(struct menu *menu); + menu_callback callback; bool exit; bool failure; }; -struct menu *menu_create(); +struct menu *menu_create(menu_callback callback); void menu_destroy(struct menu *menu); void menu_getopts(struct menu *menu, int argc, char *argv[]); void menu_add_item(struct menu *menu, char *text, bool sort); diff --git a/wayland.c b/wayland.c index d31ca20..0471fe4 100644 --- a/wayland.c +++ b/wayland.c @@ -494,11 +494,10 @@ int menu_run(struct menu *menu) { } } - bool failure = menu->failure; context_destroy(context); menu->context = NULL; - if (failure) { + if (menu->failure) { return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/wmenu-run.c b/wmenu-run.c index 11e6699..dc86b6d 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -1,6 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include -#include +#include #include #include #include @@ -27,46 +27,48 @@ static void read_items(struct menu *menu) { free(path); } -struct executable { +struct command { struct menu *menu; - char *name; + char *text; + bool exit; }; static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, const char *token) { - struct executable *exe = data; + struct command *cmd = data; xdg_activation_token_v1_destroy(activation_token); - menu_destroy(exe->menu); - setenv("XDG_ACTIVATION_TOKEN", token, true); - char* cmd[] = {"/bin/sh", "-c", exe->name, NULL}; - execvp(cmd[0], (char**)cmd); - - fprintf(stderr, "Failed to execute selection: %s\n", strerror(errno)); - free(exe->name); - free(exe); - exit(EXIT_FAILURE); + int pid = fork(); + if (pid == 0) { + setenv("XDG_ACTIVATION_TOKEN", token, true); + char *argv[] = {"/bin/sh", "-c", cmd->text, NULL}; + execvp(argv[0], (char**)argv); + } else { + if (cmd->exit) { + cmd->menu->exit = true; + } + } } static const struct xdg_activation_token_v1_listener activation_token_listener = { .done = activation_token_done, }; -static void exec(struct menu *menu) { - struct executable *exe = calloc(1, sizeof(struct executable)); - exe->menu = menu; - exe->name = strdup(menu->input); +static void exec_item(struct menu *menu, char *text, bool exit) { + struct command *cmd = calloc(1, sizeof(struct command)); + cmd->menu = menu; + cmd->text = strdup(text); + cmd->exit = exit; struct xdg_activation_v1 *activation = context_get_xdg_activation(menu->context); struct xdg_activation_token_v1 *activation_token = xdg_activation_v1_get_activation_token(activation); xdg_activation_token_v1_set_surface(activation_token, context_get_surface(menu->context)); - xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, exe); + xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, cmd); xdg_activation_token_v1_commit(activation_token); } int main(int argc, char *argv[]) { - struct menu *menu = menu_create(); - menu->callback = exec; + struct menu *menu = menu_create(exec_item); menu_getopts(menu, argc, argv); read_items(menu); int status = menu_run(menu); diff --git a/wmenu.c b/wmenu.c index e7d37d9..0ce9d08 100644 --- a/wmenu.c +++ b/wmenu.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include "menu.h" @@ -16,8 +17,16 @@ static void read_items(struct menu *menu) { } } +static void print_item(struct menu *menu, char *text, bool exit) { + puts(text); + fflush(stdout); + if (exit) { + menu->exit = true; + } +} + int main(int argc, char *argv[]) { - struct menu *menu = menu_create(); + struct menu *menu = menu_create(print_item); menu_getopts(menu, argc, argv); read_items(menu); int status = menu_run(menu); From 8b2381126319b8e50bcd697b6a7be5bf0ee7e226 Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 9 Jun 2024 20:33:37 -0400 Subject: [PATCH 06/15] Version 0.1.9 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d2643fc..a3b507d 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.1.8', + version: '0.1.9', license: 'MIT', default_options: [ 'c_std=c11', From 12b8f83be447379eded03c6109fe944945cd48aa Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 3 Aug 2024 18:26:59 -0400 Subject: [PATCH 07/15] Display over fullscreen applications --- wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index 0471fe4..0d3f261 100644 --- a/wayland.c +++ b/wayland.c @@ -439,7 +439,7 @@ int menu_run(struct menu *menu) { context->layer_shell, context->surface, context->output ? context->output->output : NULL, - ZWLR_LAYER_SHELL_V1_LAYER_TOP, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "menu" ); assert(layer_surface != NULL); From 260eaba88ec8f54fe2bdbe391b18fcd2db70836f Mon Sep 17 00:00:00 2001 From: M Stoeckl Date: Thu, 31 Oct 2024 09:23:26 -0400 Subject: [PATCH 08/15] Optimize menu sorting Sorting and deduplicating elements after all items have been registered improves the time complexity of constructing the item list from O(n^2) to O(n log n). On a system with about 4000 menu items, this reduces startup time from about 0.21 seconds to 0.13 seconds. --- menu.c | 76 +++++++++++++++++++++++++++++------------------------ menu.h | 8 +++--- render.c | 3 ++- wmenu-run.c | 3 ++- wmenu.c | 2 +- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/menu.c b/menu.c index efd3e4a..52f0810 100644 --- a/menu.c +++ b/menu.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -45,18 +46,12 @@ static void free_pages(struct menu *menu) { } } -static void free_item(struct item *item) { - free(item->text); - free(item); -} - static void free_items(struct menu *menu) { - struct item *next = menu->items; - while (next) { - struct item *item = next; - next = item->next; - free_item(item); + for (size_t i = 0; i < menu->item_count; i++) { + struct item *item = &menu->items[i]; + free(item->text); } + free(menu->items); } // Destroys the menu, freeing memory associated with it. @@ -167,34 +162,44 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { } // Add an item to the menu. -void menu_add_item(struct menu *menu, char *text, bool sort) { - struct item *new = calloc(1, sizeof(struct item)); - if (!new) { - return; +void menu_add_item(struct menu *menu, char *text) { + if ((menu->item_count & (menu->item_count - 1)) == 0) { + size_t alloc_size = menu->item_count ? 2 * menu->item_count : 1; + void *new_array = realloc(menu->items, sizeof(struct item) * alloc_size); + if (!new_array) { + fprintf(stderr, "could not realloc %zu bytes", sizeof(struct item) * alloc_size); + exit(EXIT_FAILURE); + } + menu->items = new_array; } + + struct item *new = &menu->items[menu->item_count]; new->text = text; - if (sort) { - for (struct item **item = &menu->items; *item; item = &(*item)->next) { - int result = strcmp(new->text, (*item)->text); - if (result == 0) { - free_item(new); - return; - } - if (result < 0) { - new->next = *item; - *item = new; - return; - } + menu->item_count++; +} + +static int compare_items(const void *a, const void *b) { + const struct item *item_a = a; + const struct item *item_b = b; + return strcmp(item_a->text, item_b->text); +} + +void menu_sort_and_deduplicate(struct menu *menu) { + size_t j = 1; + size_t i; + + qsort(menu->items, menu->item_count, sizeof(*menu->items), compare_items); + + for (i = 1; i < menu->item_count; i++) { + if (strcmp(menu->items[i].text, menu->items[j - 1].text) == 0) { + free(menu->items[i].text); + } else { + menu->items[j] = menu->items[i]; + j++; } } - - if (menu->lastitem) { - menu->lastitem->next = new; - } else { - menu->items = new; - } - menu->lastitem = new; + menu->item_count = j; } static void append_page(struct page *page, struct page **first, struct page **last) { @@ -291,6 +296,7 @@ static void match_items(struct menu *menu) { char buf[sizeof menu->input], *tok; char **tokv = NULL; int i, tokc = 0; + size_t k; size_t tok_len; menu->matches = NULL; menu->matches_end = NULL; @@ -314,8 +320,8 @@ static void match_items(struct menu *menu) { } tok_len = tokc ? strlen(tokv[0]) : 0; - struct item *item; - for (item = menu->items; item; item = item->next) { + for (k = 0; k < menu->item_count; k++) { + struct item *item = &menu->items[k]; for (i = 0; i < tokc; i++) { if (!fstrstr(menu, item->text, tokv[i])) { /* token does not match */ diff --git a/menu.h b/menu.h index 261c2f2..280cdd0 100644 --- a/menu.h +++ b/menu.h @@ -13,7 +13,6 @@ typedef void (*menu_callback)(struct menu *menu, char *text, bool exit); struct item { char *text; int width; - struct item *next; // traverses all items struct item *prev_match; // previous matching item struct item *next_match; // next matching item struct page *page; // the page holding this item @@ -64,8 +63,8 @@ struct menu { char input[BUFSIZ]; size_t cursor; - struct item *items; // list of all items - struct item *lastitem; // last item in the list + struct item *items; // array of all items + size_t item_count; struct item *matches; // list of matching items struct item *matches_end; // last matching item struct item *sel; // selected item @@ -79,7 +78,8 @@ struct menu { struct menu *menu_create(menu_callback callback); void menu_destroy(struct menu *menu); void menu_getopts(struct menu *menu, int argc, char *argv[]); -void menu_add_item(struct menu *menu, char *text, bool sort); +void menu_add_item(struct menu *menu, char *text); +void menu_sort_and_deduplicate(struct menu *menu); void menu_render_items(struct menu *menu); void menu_paste(struct menu *menu, const char *text, ssize_t len); void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, diff --git a/render.c b/render.c index 3813af5..070fad9 100644 --- a/render.c +++ b/render.c @@ -28,7 +28,8 @@ void calc_widths(struct menu *menu) { menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding; // Calculate item widths and input area width - for (struct item *item = menu->items; item; item = item->next) { + for (size_t i = 0; i < menu->item_count; i++) { + struct item *item = &menu->items[i]; item->width = text_width(cairo, menu->font, item->text); if (item->width > menu->inputw) { menu->inputw = item->width; diff --git a/wmenu-run.c b/wmenu-run.c index dc86b6d..1b7b8c1 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -20,10 +20,11 @@ static void read_items(struct menu *menu) { if (ent->d_name[0] == '.') { continue; } - menu_add_item(menu, strdup(ent->d_name), true); + menu_add_item(menu, strdup(ent->d_name)); } closedir(dir); } + menu_sort_and_deduplicate(menu); free(path); } diff --git a/wmenu.c b/wmenu.c index 0ce9d08..38e78b9 100644 --- a/wmenu.c +++ b/wmenu.c @@ -13,7 +13,7 @@ static void read_items(struct menu *menu) { if (p) { *p = '\0'; } - menu_add_item(menu, strdup(buf), false); + menu_add_item(menu, strdup(buf)); } } From 0947765fc9a4f6fc4287acfcd2efcaf4fef1ffb8 Mon Sep 17 00:00:00 2001 From: M Stoeckl Date: Thu, 31 Oct 2024 10:27:47 -0400 Subject: [PATCH 09/15] Only call render_menu once per frame An actual surface is not needed to estimate font sizes; a 1x1 image will do, as long as the cairo context has the same options. --- menu.c | 5 ++++- menu.h | 5 +++++ render.c | 9 +++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/menu.c b/menu.c index 52f0810..9bda76c 100644 --- a/menu.c +++ b/menu.c @@ -34,6 +34,8 @@ struct menu *menu_create(menu_callback callback) { menu->selectionbg = 0x005577ff; menu->selectionfg = 0xeeeeeeff; menu->callback = callback; + menu->test_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + menu->test_cairo = cairo_create(menu->test_surface); return menu; } @@ -58,6 +60,8 @@ static void free_items(struct menu *menu) { void menu_destroy(struct menu *menu) { free_pages(menu); free_items(menu); + cairo_destroy(menu->test_cairo); + cairo_surface_destroy(menu->test_surface); free(menu); } @@ -374,7 +378,6 @@ static void match_items(struct menu *menu) { // Render menu items. void menu_render_items(struct menu *menu) { - render_menu(menu); calc_widths(menu); match_items(menu); render_menu(menu); diff --git a/menu.h b/menu.h index 280cdd0..8dcd99c 100644 --- a/menu.h +++ b/menu.h @@ -1,6 +1,7 @@ #ifndef WMENU_MENU_H #define WMENU_MENU_H +#include #include #include #include @@ -51,6 +52,10 @@ struct menu { struct wl_context *context; + // 1x1 surface used estimate text sizes with pango + cairo_surface_t *test_surface; + cairo_t *test_cairo; + int width; int height; int line_height; diff --git a/render.c b/render.c index 070fad9..63dc4ab 100644 --- a/render.c +++ b/render.c @@ -13,8 +13,13 @@ // Calculate text widths. void calc_widths(struct menu *menu) { struct wl_context *context = menu->context; - struct pool_buffer *current = context_get_current_buffer(context); - cairo_t *cairo = current->cairo; + int scale = context_get_scale(context); + cairo_surface_set_device_scale(menu->test_surface, scale, scale); + cairo_set_antialias(menu->test_cairo, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_set_font_options(menu->test_cairo, fo); + cairo_font_options_destroy(fo); + cairo_t *cairo = menu->test_cairo; // Calculate prompt width if (menu->prompt) { From 48ec172b4bd51da0736c24d84c72b574e4107b31 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 1 Nov 2024 19:35:39 -0400 Subject: [PATCH 10/15] README: Update meson instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84dba3e..62a34a7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Dependencies: - scdoc (optional) ``` -$ meson build +$ meson setup build $ ninja -C build # ninja -C build install ``` From 3ad4b5ca3f9f66c71ec521d0d7261228ec7b4631 Mon Sep 17 00:00:00 2001 From: M Stoeckl Date: Wed, 6 Nov 2024 14:31:46 -0500 Subject: [PATCH 11/15] Simplify render_menu --- render.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/render.c b/render.c index 63dc4ab..eca498f 100644 --- a/render.c +++ b/render.c @@ -189,41 +189,22 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { void render_menu(struct menu *menu) { struct wl_context *context = menu->context; - cairo_surface_t *recorder = cairo_recording_surface_create( - CAIRO_CONTENT_COLOR_ALPHA, NULL); - cairo_t *cairo = cairo_create(recorder); - cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); - cairo_font_options_t *fo = cairo_font_options_create(); - cairo_set_font_options(cairo, fo); - cairo_font_options_destroy(fo); - cairo_save(cairo); - cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); - cairo_paint(cairo); - cairo_restore(cairo); - - render_to_cairo(menu, cairo); - int scale = context_get_scale(context); struct pool_buffer *buffer = context_get_next_buffer(context, scale); if (!buffer) { - goto cleanup; + return; } cairo_t *shm = buffer->cairo; - cairo_save(shm); - cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR); - cairo_paint(shm); - cairo_restore(shm); - cairo_set_source_surface(shm, recorder, 0, 0); - cairo_paint(shm); + cairo_set_antialias(shm, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_set_font_options(shm, fo); + cairo_font_options_destroy(fo); + render_to_cairo(menu, shm); struct wl_surface *surface = context_get_surface(context); wl_surface_set_buffer_scale(surface, scale); wl_surface_attach(surface, buffer->buffer, 0, 0); wl_surface_damage(surface, 0, 0, menu->width, menu->height); wl_surface_commit(surface); - -cleanup: - cairo_destroy(cairo); - cairo_surface_destroy(recorder); } From e2542d34ed15308545125fac9bf324b32e7cf578 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 16 Dec 2024 10:56:51 -0500 Subject: [PATCH 12/15] Render frame on surface enter This ensures that the menu is rendered with the correct scale. Fixes #14 --- wayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wayland.c b/wayland.c index 0d3f261..6ce5b77 100644 --- a/wayland.c +++ b/wayland.c @@ -208,6 +208,7 @@ static void noop() { static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { struct wl_context *context = data; context->output = wl_output_get_user_data(wl_output); + menu_render_items(context->menu); } static const struct wl_surface_listener surface_listener = { From eec775fad7f7b090e6de2dbfe0c242129fddba41 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 21 Feb 2025 12:09:16 -0500 Subject: [PATCH 13/15] Revert "Render frame on surface enter" This reverts commit e2542d34ed15308545125fac9bf324b32e7cf578. This commit causes crashes on some systems. --- wayland.c | 1 - 1 file changed, 1 deletion(-) diff --git a/wayland.c b/wayland.c index 6ce5b77..0d3f261 100644 --- a/wayland.c +++ b/wayland.c @@ -208,7 +208,6 @@ static void noop() { static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { struct wl_context *context = data; context->output = wl_output_get_user_data(wl_output); - menu_render_items(context->menu); } static const struct wl_surface_listener surface_listener = { From fc69aa6e2bccca461a0bd0c10b448b64ccda1d42 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 1 Mar 2025 07:04:00 -0500 Subject: [PATCH 14/15] Render menu after surface enter event This fixes an issue where the first visible frame is blurry on fractional scale displays. --- menu.c | 49 +++++++++++++++++++++++++++---------------------- menu.h | 2 ++ render.c | 2 ++ wayland.c | 8 +++++++- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/menu.c b/menu.c index 9bda76c..207d71c 100644 --- a/menu.c +++ b/menu.c @@ -376,6 +376,11 @@ static void match_items(struct menu *menu) { } } +// Marks the menu as needing to be rendered again. +void menu_invalidate(struct menu *menu) { + menu->rendered = false; +} + // Render menu items. void menu_render_items(struct menu *menu) { calc_widths(menu); @@ -498,13 +503,13 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, // Delete right menu->input[menu->cursor] = '\0'; match_items(menu); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_u: // Delete left insert(menu, NULL, 0 - menu->cursor); match_items(menu); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_w: // Delete word @@ -515,7 +520,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } match_items(menu); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_Y: // Paste clipboard @@ -523,17 +528,17 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; } match_items(menu); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_Left: case XKB_KEY_KP_Left: movewordedge(menu, -1); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_Right: case XKB_KEY_KP_Right: movewordedge(menu, +1); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_Return: @@ -547,11 +552,11 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, switch (sym) { case XKB_KEY_b: movewordedge(menu, -1); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_f: movewordedge(menu, +1); - render_menu(menu); + menu_invalidate(menu); return; case XKB_KEY_g: sym = XKB_KEY_Home; @@ -593,10 +598,10 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_KP_Up: if (menu->sel && menu->sel->prev_match) { menu->sel = menu->sel->prev_match; - render_menu(menu); + menu_invalidate(menu); } else if (menu->cursor > 0) { menu->cursor = nextrune(menu, -1); - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_Right: @@ -605,51 +610,51 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_KP_Down: if (menu->cursor < len) { menu->cursor = nextrune(menu, +1); - render_menu(menu); + menu_invalidate(menu); } else if (menu->sel && menu->sel->next_match) { menu->sel = menu->sel->next_match; - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_Prior: case XKB_KEY_KP_Prior: if (menu->sel && menu->sel->page->prev) { menu->sel = menu->sel->page->prev->first; - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_Next: case XKB_KEY_KP_Next: if (menu->sel && menu->sel->page->next) { menu->sel = menu->sel->page->next->first; - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_Home: case XKB_KEY_KP_Home: if (menu->sel == menu->matches) { menu->cursor = 0; - render_menu(menu); + menu_invalidate(menu); } else { menu->sel = menu->matches; - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_End: case XKB_KEY_KP_End: if (menu->cursor < len) { menu->cursor = len; - render_menu(menu); + menu_invalidate(menu); } else { menu->sel = menu->matches_end; - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_BackSpace: if (menu->cursor > 0) { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); match_items(menu); - render_menu(menu); + menu_invalidate(menu); } break; case XKB_KEY_Delete: @@ -660,7 +665,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, menu->cursor = nextrune(menu, +1); insert(menu, NULL, nextrune(menu, -1) - menu->cursor); match_items(menu); - render_menu(menu); + menu_invalidate(menu); break; case XKB_KEY_Tab: if (!menu->sel) { @@ -670,7 +675,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, memcpy(menu->input, menu->sel->text, menu->cursor); menu->input[menu->cursor] = '\0'; match_items(menu); - render_menu(menu); + menu_invalidate(menu); break; case XKB_KEY_Escape: menu->exit = true; @@ -680,7 +685,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, if (xkb_keysym_to_utf8(sym, buf, 8)) { insert(menu, buf, strnlen(buf, 8)); match_items(menu); - render_menu(menu); + menu_invalidate(menu); } } } diff --git a/menu.h b/menu.h index 8dcd99c..fdd9ad2 100644 --- a/menu.h +++ b/menu.h @@ -64,6 +64,7 @@ struct menu { int promptw; int left_arrow; int right_arrow; + bool rendered; char input[BUFSIZ]; size_t cursor; @@ -85,6 +86,7 @@ void menu_destroy(struct menu *menu); void menu_getopts(struct menu *menu, int argc, char *argv[]); void menu_add_item(struct menu *menu, char *text); void menu_sort_and_deduplicate(struct menu *menu); +void menu_invalidate(struct menu *menu); void menu_render_items(struct menu *menu); void menu_paste(struct menu *menu, const char *text, ssize_t len); void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, diff --git a/render.c b/render.c index eca498f..e86d93b 100644 --- a/render.c +++ b/render.c @@ -207,4 +207,6 @@ void render_menu(struct menu *menu) { wl_surface_attach(surface, buffer->buffer, 0, 0); wl_surface_damage(surface, 0, 0, menu->width, menu->height); wl_surface_commit(surface); + + menu->rendered = true; } diff --git a/wayland.c b/wayland.c index 0d3f261..823ced1 100644 --- a/wayland.c +++ b/wayland.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "menu.h" #include "pool-buffer.h" +#include "render.h" #include "wayland.h" #include "xdg-activation-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -208,6 +208,7 @@ static void noop() { static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { struct wl_context *context = data; context->output = wl_output_get_user_data(wl_output); + menu_invalidate(context->menu); } static const struct wl_surface_listener surface_listener = { @@ -492,6 +493,11 @@ int menu_run(struct menu *menu) { if (fds[1].revents & POLLIN) { keyboard_repeat(context->keyboard); } + + // Render the menu if necessary + if (!menu->rendered) { + render_menu(menu); + } } context_destroy(context); From 0a38d45abba5b04775b000a8deafb141d230687b Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 29 Apr 2025 15:23:10 -0400 Subject: [PATCH 15/15] Version 0.2.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a3b507d..3a5cb18 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.1.9', + version: '0.2.0', license: 'MIT', default_options: [ 'c_std=c11',