diff --git a/README.md b/README.md index 62a34a7..c4c7c63 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Dependencies: - scdoc (optional) ``` -$ meson setup build +$ meson build $ ninja -C build # ninja -C build install ``` @@ -30,3 +30,14 @@ 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. diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 4519e8b..1ee623a 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland # SYNOPSIS -*wmenu* [-biPv] \ +*wmenu* [-biv] \ [-f _font_] \ [-l _lines_] \ [-o _output_] \ @@ -35,10 +35,6 @@ $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. - *-v* prints version information to stdout, then exits. diff --git a/menu.c b/menu.c index 207d71c..3e8f9be 100644 --- a/menu.c +++ b/menu.c @@ -1,5 +1,4 @@ #define _POSIX_C_SOURCE 200809L -#include #include #include #include @@ -23,7 +22,7 @@ #include "wayland.h" // Creates and returns a new menu. -struct menu *menu_create(menu_callback callback) { +struct menu *menu_create() { struct menu *menu = calloc(1, sizeof(struct menu)); menu->strncmp = strncmp; menu->font = "monospace 10"; @@ -33,9 +32,6 @@ struct menu *menu_create(menu_callback callback) { menu->promptfg = 0xeeeeeeff; 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; } @@ -48,20 +44,24 @@ 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) { - for (size_t i = 0; i < menu->item_count; i++) { - struct item *item = &menu->items[i]; - free(item->text); + struct item *next = menu->items; + while (next) { + struct item *item = next; + next = item->next; + free_item(item); } - free(menu->items); } // Destroys the menu, freeing memory associated with it. 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); } @@ -85,11 +85,11 @@ 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 [-biPv] [-f font] [-l lines] [-o output] [-p prompt]\n" + "Usage: wmenu [-biPvx] [-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; - while ((opt = getopt(argc, argv, "bhiPvf:l:o:p:N:n:M:m:S:s:")) != -1) { + while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) { switch (opt) { case 'b': menu->bottom = true; @@ -97,9 +97,6 @@ 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); @@ -166,44 +163,34 @@ 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) { - 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; +void menu_add_item(struct menu *menu, char *text, bool sort) { + struct item *new = calloc(1, sizeof(struct item)); + if (!new) { + return; } - - struct item *new = &menu->items[menu->item_count]; new->text = text; - 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 (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 = j; + + if (menu->lastitem) { + menu->lastitem->next = new; + } else { + menu->items = new; + } + menu->lastitem = new; } static void append_page(struct page *page, struct page **first, struct page **last) { @@ -300,7 +287,6 @@ 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; @@ -324,8 +310,8 @@ static void match_items(struct menu *menu) { } tok_len = tokc ? strlen(tokv[0]) : 0; - for (k = 0; k < menu->item_count; k++) { - struct item *item = &menu->items[k]; + struct item *item; + for (item = menu->items; item; item = item->next) { for (i = 0; i < tokc; i++) { if (!fstrstr(menu, item->text, tokv[i])) { /* token does not match */ @@ -376,13 +362,9 @@ 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) { + render_menu(menu); calc_widths(menu); match_items(menu); render_menu(menu); @@ -503,13 +485,13 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, // Delete right menu->input[menu->cursor] = '\0'; match_items(menu); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_u: // Delete left insert(menu, NULL, 0 - menu->cursor); match_items(menu); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_w: // Delete word @@ -520,7 +502,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); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_Y: // Paste clipboard @@ -528,17 +510,17 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; } match_items(menu); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_Left: case XKB_KEY_KP_Left: movewordedge(menu, -1); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_Right: case XKB_KEY_KP_Right: movewordedge(menu, +1); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_Return: @@ -552,11 +534,11 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, switch (sym) { case XKB_KEY_b: movewordedge(menu, -1); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_f: movewordedge(menu, +1); - menu_invalidate(menu); + render_menu(menu); return; case XKB_KEY_g: sym = XKB_KEY_Home; @@ -586,10 +568,18 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_Return: case XKB_KEY_KP_Enter: if (shift) { - menu->callback(menu, menu->input, true); + puts(menu->input); + fflush(stdout); + menu->exit = true; + } else if (menu->callback) { + menu->callback(menu); } else { char *text = menu->sel ? menu->sel->text : menu->input; - menu->callback(menu, text, !ctrl); + puts(text); + fflush(stdout); + if (!ctrl) { + menu->exit = true; + } } break; case XKB_KEY_Left: @@ -598,10 +588,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; - menu_invalidate(menu); + render_menu(menu); } else if (menu->cursor > 0) { menu->cursor = nextrune(menu, -1); - menu_invalidate(menu); + render_menu(menu); } break; case XKB_KEY_Right: @@ -610,51 +600,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); - menu_invalidate(menu); + render_menu(menu); } else if (menu->sel && menu->sel->next_match) { menu->sel = menu->sel->next_match; - menu_invalidate(menu); + render_menu(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; - menu_invalidate(menu); + render_menu(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; - menu_invalidate(menu); + render_menu(menu); } break; case XKB_KEY_Home: case XKB_KEY_KP_Home: if (menu->sel == menu->matches) { menu->cursor = 0; - menu_invalidate(menu); + render_menu(menu); } else { menu->sel = menu->matches; - menu_invalidate(menu); + render_menu(menu); } break; case XKB_KEY_End: case XKB_KEY_KP_End: if (menu->cursor < len) { menu->cursor = len; - menu_invalidate(menu); + render_menu(menu); } else { menu->sel = menu->matches_end; - menu_invalidate(menu); + render_menu(menu); } break; case XKB_KEY_BackSpace: if (menu->cursor > 0) { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); match_items(menu); - menu_invalidate(menu); + render_menu(menu); } break; case XKB_KEY_Delete: @@ -665,7 +655,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); - menu_invalidate(menu); + render_menu(menu); break; case XKB_KEY_Tab: if (!menu->sel) { @@ -675,7 +665,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); - menu_invalidate(menu); + render_menu(menu); break; case XKB_KEY_Escape: menu->exit = true; @@ -685,7 +675,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); - menu_invalidate(menu); + render_menu(menu); } } } diff --git a/menu.h b/menu.h index fdd9ad2..0a6b22c 100644 --- a/menu.h +++ b/menu.h @@ -1,19 +1,16 @@ #ifndef WMENU_MENU_H #define WMENU_MENU_H -#include #include #include #include #include -struct menu; -typedef void (*menu_callback)(struct menu *menu, char *text, bool exit); - // A menu item. 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 @@ -33,8 +30,6 @@ 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 @@ -52,10 +47,6 @@ 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; @@ -64,29 +55,26 @@ struct menu { int promptw; int left_arrow; int right_arrow; - bool rendered; char input[BUFSIZ]; size_t cursor; - struct item *items; // array of all items - size_t item_count; + struct item *items; // list of all items + struct item *lastitem; // last item in the list struct item *matches; // list of matching items struct item *matches_end; // last matching item struct item *sel; // selected item struct page *pages; // list of pages - menu_callback callback; + void (*callback)(struct menu *menu); bool exit; bool failure; }; -struct menu *menu_create(menu_callback callback); +struct menu *menu_create(); 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_add_item(struct menu *menu, char *text, bool sort); 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/meson.build b/meson.build index 3a5cb18..d2643fc 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.2.0', + version: '0.1.8', license: 'MIT', default_options: [ 'c_std=c11', diff --git a/render.c b/render.c index e86d93b..e33898d 100644 --- a/render.c +++ b/render.c @@ -13,13 +13,8 @@ // Calculate text widths. void calc_widths(struct menu *menu) { struct wl_context *context = menu->context; - 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; + struct pool_buffer *current = context_get_current_buffer(context); + cairo_t *cairo = current->cairo; // Calculate prompt width if (menu->prompt) { @@ -33,8 +28,7 @@ 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 (size_t i = 0; i < menu->item_count; i++) { - struct item *item = &menu->items[i]; + for (struct item *item = menu->items; item; item = item->next) { item->width = text_width(cairo, menu->font, item->text); if (item->width > menu->inputw) { menu->inputw = item->width; @@ -85,22 +79,8 @@ static void render_prompt(struct menu *menu, cairo_t *cairo) { // Renders the input text. static void render_input(struct menu *menu, cairo_t *cairo) { - 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); - } + render_text(menu, cairo, menu->input, menu->promptw, 0, 0, + 0, menu->normalfg, menu->padding, menu->padding); } // Renders a cursor for the input field. @@ -189,18 +169,33 @@ 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) { - return; + goto cleanup; } cairo_t *shm = buffer->cairo; - 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); + 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); struct wl_surface *surface = context_get_surface(context); wl_surface_set_buffer_scale(surface, scale); @@ -208,5 +203,7 @@ void render_menu(struct menu *menu) { wl_surface_damage(surface, 0, 0, menu->width, menu->height); wl_surface_commit(surface); - menu->rendered = true; +cleanup: + cairo_destroy(cairo); + cairo_surface_destroy(recorder); } diff --git a/wayland.c b/wayland.c index 823ced1..d31ca20 100644 --- a/wayland.c +++ b/wayland.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,6 @@ #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,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_invalidate(context->menu); } static const struct wl_surface_listener surface_listener = { @@ -440,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_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, "menu" ); assert(layer_surface != NULL); @@ -493,17 +492,13 @@ 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); - } } + bool failure = menu->failure; context_destroy(context); menu->context = NULL; - if (menu->failure) { + if (failure) { return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/wmenu-run.c b/wmenu-run.c index 1b7b8c1..228edc4 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -1,6 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include -#include +#include #include #include #include @@ -20,56 +20,56 @@ static void read_items(struct menu *menu) { if (ent->d_name[0] == '.') { continue; } - menu_add_item(menu, strdup(ent->d_name)); + menu_add_item(menu, strdup(ent->d_name), true); } closedir(dir); } - menu_sort_and_deduplicate(menu); free(path); } -struct command { +struct executable { struct menu *menu; - char *text; - bool exit; + char *name; }; static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, const char *token) { - struct command *cmd = data; + struct executable *exe = data; xdg_activation_token_v1_destroy(activation_token); + menu_destroy(exe->menu); - 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; - } - } + setenv("XDG_ACTIVATION_TOKEN", token, true); + execlp(exe->name, exe->name, NULL); + + fprintf(stderr, "Failed to execute selection: %s\n", strerror(errno)); + free(exe->name); + free(exe); + exit(EXIT_FAILURE); } static const struct xdg_activation_token_v1_listener activation_token_listener = { .done = activation_token_done, }; -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; +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); 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, cmd); + xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, exe); xdg_activation_token_v1_commit(activation_token); } int main(int argc, char *argv[]) { - struct menu *menu = menu_create(exec_item); + struct menu *menu = menu_create(); + menu->callback = exec; menu_getopts(menu, argc, argv); read_items(menu); int status = menu_run(menu); diff --git a/wmenu.c b/wmenu.c index 38e78b9..e7d37d9 100644 --- a/wmenu.c +++ b/wmenu.c @@ -1,6 +1,5 @@ #define _POSIX_C_SOURCE 200809L -#include #include #include "menu.h" @@ -13,20 +12,12 @@ static void read_items(struct menu *menu) { if (p) { *p = '\0'; } - menu_add_item(menu, strdup(buf)); - } -} - -static void print_item(struct menu *menu, char *text, bool exit) { - puts(text); - fflush(stdout); - if (exit) { - menu->exit = true; + menu_add_item(menu, strdup(buf), false); } } int main(int argc, char *argv[]) { - struct menu *menu = menu_create(print_item); + struct menu *menu = menu_create(); menu_getopts(menu, argc, argv); read_items(menu); int status = menu_run(menu);