From d77ff0e64de510cf0aaec92107339f5b8c997702 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 28 Dec 2023 11:26:38 -0500 Subject: [PATCH 01/72] Fix various type issues --- main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main.c b/main.c index 056e0a3..c7d3c34 100644 --- a/main.c +++ b/main.c @@ -938,12 +938,12 @@ void insert(struct menu_state *state, const char *s, ssize_t n) { match(state); } -char * fstrstr(struct menu_state *state, const char *s, const char *sub) { +const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { size_t len; for(len = strlen(sub); *s; s++) if(!state->fstrncmp(s, sub, len)) - return (char *)s; + return s; return NULL; } @@ -1116,12 +1116,12 @@ bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; } - int len = strlen(color); + size_t len = strlen(color); if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) { return false; } char *ptr; - uint32_t parsed = strtoul(color, &ptr, 16); + uint32_t parsed = (uint32_t)strtoul(color, &ptr, 16); if (*ptr != '\0') { return false; } @@ -1225,7 +1225,7 @@ int main(int argc, char **argv) { { wl_display_get_fd(state.display), POLLIN }, { state.repeat_timer, POLLIN }, }; - const int nfds = sizeof(fds) / sizeof(*fds); + const size_t nfds = sizeof(fds) / sizeof(*fds); while (state.run) { errno = 0; From 3ec74a0f2f7aebbedffaa74b6caa3d8299f65f08 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 28 Dec 2023 11:28:08 -0500 Subject: [PATCH 02/72] pool-buffer: Reduce struct padding --- pool-buffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pool-buffer.h b/pool-buffer.h index 67a643c..1549041 100644 --- a/pool-buffer.h +++ b/pool-buffer.h @@ -12,8 +12,8 @@ struct pool_buffer { PangoContext *pango; size_t size; int32_t width, height, scale; - void *data; bool busy; + void *data; }; struct pool_buffer *get_next_buffer(struct wl_shm *shm, From 69a7078e019261b26c31b61724a0f3bc517ab624 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 28 Dec 2023 11:42:50 -0500 Subject: [PATCH 03/72] Check the return value of pipe On some systems, pipe is declared with the attribute warn_unused_result, so we have to check the return value. --- main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index c7d3c34..b30edbf 100644 --- a/main.c +++ b/main.c @@ -573,7 +573,10 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, } int fds[2]; - pipe(fds); + if (pipe(fds) == -1) { + // Pipe failed + return; + } wl_data_offer_receive(state->offer, "text/plain", fds[1]); close(fds[1]); From d139ebae8fd3d545f586470ff04008f311e47c12 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 28 Dec 2023 11:59:02 -0500 Subject: [PATCH 04/72] pool-buffer: Fix type conversion issues --- pool-buffer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pool-buffer.c b/pool-buffer.c index aca95a1..a4cf513 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -68,19 +68,19 @@ static const struct wl_buffer_listener buffer_listener = { static struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, int32_t width, int32_t height, int32_t scale, uint32_t format) { - uint32_t stride = width * scale * 4; - size_t size = stride * height * scale; + int32_t stride = width * scale * 4; + int32_t size = stride * height * scale; int fd = create_shm_file(size); assert(fd != -1); - void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + void *data = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); buf->buffer = wl_shm_pool_create_buffer(pool, 0, width * scale, height * scale, stride, format); wl_shm_pool_destroy(pool); close(fd); - buf->size = size; + buf->size = (size_t)size; buf->width = width; buf->height = height; buf->scale = scale; From 1ef3f6a9b6eb51d08ac1ad3a6deb7bbf8dd573d9 Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 21 Jan 2024 19:50:34 -0500 Subject: [PATCH 05/72] Version 0.1.6 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5607d42..2350694 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.1.5', + version: '0.1.6', license: 'MIT', default_options: [ 'c_std=c11', From 5ef1e637bfe3b9e2f58cc15d8a0dd0256e1b93d0 Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 4 Feb 2024 15:47:18 -0500 Subject: [PATCH 06/72] Make scdoc dependency optional --- docs/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/meson.build b/docs/meson.build index 4ccfe49..39498d8 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -1,4 +1,4 @@ -scdoc_dep = dependency('scdoc', version: '>=1.9.2', native: true) +scdoc_dep = dependency('scdoc', version: '>=1.9.2', native: true, required: false) if scdoc_dep.found() scdoc = find_program( From cb884725f61b31e861f667d7517978ea55d1279f Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Sun, 25 Feb 2024 14:49:53 -0500 Subject: [PATCH 07/72] Update keybindings to more closely follow dmenu There's no need to distinguish between vertical and horizontal mode for the directional keys. By not doing so we match dmenu's behaviour and also reduce code duplication. --- docs/wmenu.1.scd | 3 +++ main.c | 48 +++++------------------------------------------- 2 files changed, 8 insertions(+), 43 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index f532b21..5fbf50e 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -112,6 +112,9 @@ arrow keys, page up, page down, home, and end. |[ *C-g* :[ Escape +|[ *C-[* +:[ Escape + |[ *C-h* :[ Backspace diff --git a/main.c b/main.c index b30edbf..3bc0f0a 100644 --- a/main.c +++ b/main.c @@ -525,6 +525,9 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, case XKB_KEY_g: sym = XKB_KEY_Escape; break; + case XKB_KEY_bracketleft: + sym = XKB_KEY_Escape; + break; case XKB_KEY_h: sym = XKB_KEY_BackSpace; break; @@ -641,9 +644,8 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, break; case XKB_KEY_Left: case XKB_KEY_KP_Left: - if (state->vertical) { - break; - } + case XKB_KEY_Up: + case XKB_KEY_KP_Up: if (state->cursor && (!state->selection || !state->selection->left)) { state->cursor = nextrune(state, -1); render_frame(state); @@ -660,48 +662,8 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, break; case XKB_KEY_Right: case XKB_KEY_KP_Right: - if (state->vertical) { - break; - } - if (state->cursor < len) { - state->cursor = nextrune(state, +1); - render_frame(state); - } else if (state->cursor == len) { - if (state->selection && state->selection->right) { - if (state->selection == state->rightmost) { - state->leftmost = state->selection->right; - state->rightmost = NULL; - } - state->selection = state->selection->right; - scroll_matches(state); - render_frame(state); - } - } - break; - case XKB_KEY_Up: - case XKB_KEY_KP_Up: - if (!state->vertical) { - break; - } - if (state->cursor && (!state->selection || !state->selection->left)) { - state->cursor = nextrune(state, -1); - render_frame(state); - } - if (state->selection && state->selection->left) { - if (state->selection == state->leftmost) { - state->rightmost = state->selection->left; - state->leftmost = NULL; - } - state->selection = state->selection->left; - scroll_matches(state); - render_frame(state); - } - break; case XKB_KEY_Down: case XKB_KEY_KP_Down: - if (!state->vertical) { - break; - } if (state->cursor < len) { state->cursor = nextrune(state, +1); render_frame(state); From 542c307ef23a97f6f0ed255df51fcbe9b23ed07c Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 06:59:09 -0500 Subject: [PATCH 08/72] Ignore unrecognized Ctrl keybindings Currently, unrecognized Ctrl keybindings are treated as if Ctrl wasn't pressed. For example, Ctrl+q results in q being typed. Instead, ignore these keypresses. --- main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.c b/main.c index 3bc0f0a..22a1e52 100644 --- a/main.c +++ b/main.c @@ -621,6 +621,12 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, } render_frame(state); return; + + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + break; + default: + return; } } From 906b55019e50558e7d2ee0b26b9732b64b6306e1 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 10:43:52 -0500 Subject: [PATCH 09/72] Keep track of end of match list --- main.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/main.c b/main.c index 22a1e52..7be3762 100644 --- a/main.c +++ b/main.c @@ -90,6 +90,7 @@ struct menu_state { struct menu_item *items; struct menu_item *matches; + struct menu_item *matchend; struct menu_item *selection; struct menu_item *leftmost, *rightmost; }; @@ -726,14 +727,9 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, state->cursor = len; render_frame(state); } else { - if (!state->selection || !state->selection->right) { - return; - } - while (state->selection && state->selection->right) { - state->selection = state->selection->right; - } + state->selection = state->matchend; + state->rightmost = state->matchend; state->leftmost = NULL; - state->rightmost = state->selection; scroll_matches(state); render_frame(state); } @@ -932,6 +928,7 @@ void match(struct menu_state *state) { struct menu_item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; state->matches = NULL; + state->matchend = NULL; state->leftmost = NULL; size_t len = strlen(state->text); state->matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; @@ -962,11 +959,12 @@ void match(struct menu_state *state) { if (itemend) { itemend->right = lsubstr; lsubstr->left = itemend; - itemend = substrend; } else { state->matches = lsubstr; } + itemend = substrend; } + state->matchend = itemend; state->selection = state->matches; state->leftmost = state->matches; state->rightmost = NULL; From d23a2c563a5e9b722a07c22d33f1898793627a40 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 10:47:35 -0500 Subject: [PATCH 10/72] Simplify match scrolling --- main.c | 88 +++++++++++++++++++--------------------------------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/main.c b/main.c index 7be3762..4202e4b 100644 --- a/main.c +++ b/main.c @@ -183,79 +183,47 @@ void scroll_matches(struct menu_state *state) { return; } + if (state->leftmost == NULL && state->rightmost == NULL) { + state->leftmost = state->matches; + } + if (state->vertical) { if (state->leftmost == NULL) { - state->leftmost = state->matches; - if (state->rightmost == NULL) { - int offs = 0; - struct menu_item *item; - for (item = state->matches; item->left != state->selection; item = item->right) { - offs += state->line_height; - if (offs >= state->height) { - state->leftmost = item->left; - offs = state->height - offs; - } - } - } else { - int offs = 0; - struct menu_item *item; - for (item = state->rightmost; item; item = item->left) { - offs += state->line_height; - if (offs >= state->height) { - state->leftmost = item->right; - break; - } - } + struct menu_item *item = state->rightmost; + for (int i = 1; item->left && i < state->lines; i++) { + item = item->left; } - } - if (state->rightmost == NULL) { - state->rightmost = state->matches; - int offs = 0; - struct menu_item *item; - for (item = state->leftmost; item; item = item->right) { - offs += state->line_height; - if (offs >= state->height) { - break; - } - state->rightmost = item; + state->leftmost = item; + } else if (state->rightmost == NULL) { + struct menu_item *item = state->leftmost; + for (int i = 1; item->right && i < state->lines; i++) { + item = item->right; } + state->rightmost = item; } } else { // Calculate available space - int padding = state->padding; - int width = state->width - state->inputw - state->promptw + int max_width = state->width - state->inputw - state->promptw - state->left_arrow - state->right_arrow; + if (state->leftmost == NULL) { - state->leftmost = state->matches; - if (state->rightmost == NULL) { - int offs = 0; - struct menu_item *item; - for (item = state->matches; item->left != state->selection; item = item->right) { - offs += item->width + 2 * padding; - if (offs >= width) { - state->leftmost = item->left; - offs = width - offs; - } - } - } else { - int offs = 0; - struct menu_item *item; - for (item = state->rightmost; item; item = item->left) { - offs += item->width + 2 * padding; - if (offs >= width) { - state->leftmost = item->right; - break; - } + state->leftmost = state->rightmost; + int total_width = 0; + struct menu_item *item; + for (item = state->rightmost; item; item = item->left) { + total_width += item->width + 2 * state->padding; + if (total_width > max_width) { + break; } + state->leftmost = item; } - } - if (state->rightmost == NULL) { - state->rightmost = state->matches; - int offs = 0; + } else if (state->rightmost == NULL) { + state->rightmost = state->leftmost; + int total_width = 0; struct menu_item *item; for (item = state->leftmost; item; item = item->right) { - offs += item->width + 2 * padding; - if (offs >= width) { + total_width += item->width + 2 * state->padding; + if (total_width > max_width) { break; } state->rightmost = item; From 07ac84239ef655cf53ac14f9dcce330a6e7e9791 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 12:40:11 -0500 Subject: [PATCH 11/72] Refactor item paging logic Determine which items go on which page ahead of time to avoid calculating it every time. This also fixes an issue where paging from the back doesn't give the same results as paging from the front. --- main.c | 218 +++++++++++++++++++++++++++------------------------------ 1 file changed, 103 insertions(+), 115 deletions(-) diff --git a/main.c b/main.c index 4202e4b..ce0fc86 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,14 @@ struct menu_item { int width; struct menu_item *next; // traverses all items struct menu_item *left, *right; // traverses matching items + struct item_group *group; +}; + +struct item_group { + struct menu_item *first; + struct menu_item *last; + struct item_group *prev; + struct item_group *next; }; struct output { @@ -89,10 +97,10 @@ struct menu_state { bool failure; struct menu_item *items; - struct menu_item *matches; + struct menu_item *matchstart; struct menu_item *matchend; struct menu_item *selection; - struct menu_item *leftmost, *rightmost; + struct item_group *groups; }; static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { @@ -178,56 +186,69 @@ void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char * pango_printf(cairo, state->font, 1, str); } -void scroll_matches(struct menu_state *state) { - if (!state->matches) { +void append_group(struct item_group *group, struct item_group **first, struct item_group **last) { + if (*last) { + (*last)->next = group; + } else { + *first = group; + } + group->prev = *last; + group->next = NULL; + *last = group; +} + +void group_items(struct menu_state *state) { + // Free existing groups + struct item_group *group = state->groups; + while (group != NULL) { + struct item_group *current = group; + group = group->next; + free(current); + } + state->groups = NULL; + + if (!state->matchstart) { return; } - if (state->leftmost == NULL && state->rightmost == NULL) { - state->leftmost = state->matches; - } - + // Make new item groups if (state->vertical) { - if (state->leftmost == NULL) { - struct menu_item *item = state->rightmost; - for (int i = 1; item->left && i < state->lines; i++) { - item = item->left; - } - state->leftmost = item; - } else if (state->rightmost == NULL) { - struct menu_item *item = state->leftmost; - for (int i = 1; item->right && i < state->lines; i++) { + struct item_group *groupend = NULL; + struct menu_item *item = state->matchstart; + while (item) { + struct item_group *group = calloc(1, sizeof(struct item_group)); + group->first = item; + + for (int i = 1; item && i <= state->lines; i++) { + item->group = group; + group->last = item; item = item->right; } - state->rightmost = item; + append_group(group, &state->groups, &groupend); } } else { // Calculate available space int max_width = state->width - state->inputw - state->promptw - state->left_arrow - state->right_arrow; - if (state->leftmost == NULL) { - state->leftmost = state->rightmost; + struct item_group *groupend = NULL; + struct menu_item *item = state->matchstart; + while (item) { + struct item_group *group = calloc(1, sizeof(struct item_group)); + group->first = item; + int total_width = 0; - struct menu_item *item; - for (item = state->rightmost; item; item = item->left) { + while (item) { total_width += item->width + 2 * state->padding; if (total_width > max_width) { break; } - state->leftmost = item; - } - } else if (state->rightmost == NULL) { - state->rightmost = state->leftmost; - int total_width = 0; - struct menu_item *item; - for (item = state->leftmost; item; item = item->right) { - total_width += item->width + 2 * state->padding; - if (total_width > max_width) { - break; - } - state->rightmost = item; + + item->group = group; + group->last = item; + item = item->right; } + append_group(group, &state->groups, &groupend); } } } @@ -236,7 +257,6 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { int width = state->width; int padding = state->padding; - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_u32(cairo, state->background); cairo_paint(cairo); @@ -275,7 +295,7 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { cairo_fill(cairo); } - if (!state->matches) { + if (!state->matchstart) { return; } @@ -283,16 +303,13 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { // Draw matches vertically int y = state->line_height; struct menu_item *item; - for (item = state->leftmost; item; item = item->right) { + for (item = state->selection->group->first; item != state->selection->group->last->right; item = item->right) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; render_vertical_item(state, cairo, item->text, x, y, width, state->line_height, fg_color, bg_color, padding); y += state->line_height; - if (y >= state->height) { - break; - } } } else { // Leave room for input @@ -307,28 +324,24 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { x += state->left_arrow; // Draw matches horizontally - bool scroll_right = false; struct menu_item *item; - for (item = state->leftmost; item; item = item->right) { + for (item = state->selection->group->first; item != state->selection->group->last->right; item = item->right) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; x = render_horizontal_item(state, cairo, item->text, x, 0, width - state->right_arrow, state->line_height, fg_color, bg_color, padding, padding); - if (x == -1) { - scroll_right = true; - break; - } + // TODO: Make sure render_horizontal_item doesn't return -1 } // Draw left scroll indicator if necessary - if (state->leftmost != state->matches) { + if (state->selection->group->prev) { cairo_move_to(cairo, left_arrow_pos, 0); pango_printf(cairo, state->font, 1, "<"); } // Draw right scroll indicator if necessary - if (scroll_right) { + if (state->selection->group->next) { cairo_move_to(cairo, width - state->right_arrow + padding, 0); pango_printf(cairo, state->font, 1, ">"); } @@ -621,17 +634,11 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, case XKB_KEY_KP_Left: case XKB_KEY_Up: case XKB_KEY_KP_Up: - if (state->cursor && (!state->selection || !state->selection->left)) { - state->cursor = nextrune(state, -1); - render_frame(state); - } if (state->selection && state->selection->left) { - if (state->selection == state->leftmost) { - state->rightmost = state->selection->left; - state->leftmost = NULL; - } state->selection = state->selection->left; - scroll_matches(state); + render_frame(state); + } else if (state->cursor > 0) { + state->cursor = nextrune(state, -1); render_frame(state); } break; @@ -642,50 +649,32 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, if (state->cursor < len) { state->cursor = nextrune(state, +1); render_frame(state); - } else if (state->cursor == len) { - if (state->selection && state->selection->right) { - if (state->selection == state->rightmost) { - state->leftmost = state->selection->right; - state->rightmost = NULL; - } - state->selection = state->selection->right; - scroll_matches(state); - render_frame(state); - } + } else if (state->selection && state->selection->right) { + state->selection = state->selection->right; + render_frame(state); } break; case XKB_KEY_Page_Up: case XKB_KEY_KP_Page_Up: - if (state->leftmost && state->leftmost->left) { - state->rightmost = state->leftmost->left; - state->leftmost = NULL; - scroll_matches(state); - state->selection = state->leftmost; + if (state->selection->group->prev) { + state->selection = state->selection->group->prev->first; render_frame(state); } break; case XKB_KEY_Page_Down: case XKB_KEY_KP_Page_Down: - if (state->rightmost && state->rightmost->right) { - state->leftmost = state->rightmost->right; - state->rightmost = NULL; - state->selection = state->leftmost; - scroll_matches(state); + if (state->selection->group->next) { + state->selection = state->selection->group->next->first; render_frame(state); } break; case XKB_KEY_Home: case XKB_KEY_KP_Home: - if (state->selection == state->matches) { - if (state->cursor != 0) { - state->cursor = 0; - render_frame(state); - } + if (state->selection == state->matchstart) { + state->cursor = 0; + render_frame(state); } else { - state->selection = state->matches; - state->leftmost = state->matches; - state->rightmost = NULL; - scroll_matches(state); + state->selection = state->matchstart; render_frame(state); } break; @@ -696,9 +685,6 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, render_frame(state); } else { state->selection = state->matchend; - state->rightmost = state->matchend; - state->leftmost = NULL; - scroll_matches(state); render_frame(state); } break; @@ -882,24 +868,28 @@ const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { return NULL; } -void append_item(struct menu_item *item, struct menu_item **list, struct menu_item **last) { - if(!*last) - *list = item; - else +void append_item(struct menu_item *item, struct menu_item **first, struct menu_item **last) { + if (*last) { (*last)->right = item; + } else { + *first = item; + } item->left = *last; item->right = NULL; *last = item; } void match(struct menu_state *state) { - struct menu_item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; - - state->matches = NULL; + struct menu_item *lexact = NULL, *exactend = NULL; + struct menu_item *lprefix = NULL, *prefixend = NULL; + struct menu_item *lsubstr = NULL, *substrend = NULL; + state->matchstart = NULL; state->matchend = NULL; - state->leftmost = NULL; + state->selection = NULL; + size_t len = strlen(state->text); - state->matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; + + struct menu_item *item; for (item = state->items; item; item = item->next) { if (!state->fstrncmp(state->text, item->text, len + 1)) { append_item(item, &lexact, &exactend); @@ -911,32 +901,30 @@ void match(struct menu_state *state) { } if (lexact) { - state->matches = lexact; - itemend = exactend; + state->matchstart = lexact; + state->matchend = exactend; } if (lprefix) { - if (itemend) { - itemend->right = lprefix; - lprefix->left = itemend; + if (state->matchend) { + state->matchend->right = lprefix; + lprefix->left = state->matchend; } else { - state->matches = lprefix; + state->matchstart = lprefix; } - itemend = prefixend; + state->matchend = prefixend; } if (lsubstr) { - if (itemend) { - itemend->right = lsubstr; - lsubstr->left = itemend; + if (state->matchend) { + state->matchend->right = lsubstr; + lsubstr->left = state->matchend; } else { - state->matches = lsubstr; + state->matchstart = lsubstr; } - itemend = substrend; + state->matchend = substrend; } - state->matchend = itemend; - state->selection = state->matches; - state->leftmost = state->matches; - state->rightmost = NULL; - scroll_matches(state); + + group_items(state); + state->selection = state->groups->first; } size_t nextrune(struct menu_state *state, int incr) { From 9edefe1344725274bd05cafb448804f5a6ce737b Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:06:42 -0500 Subject: [PATCH 12/72] Rename item_group to page --- main.c | 162 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 80 insertions(+), 82 deletions(-) diff --git a/main.c b/main.c index ce0fc86..037161f 100644 --- a/main.c +++ b/main.c @@ -27,14 +27,14 @@ struct menu_item { int width; struct menu_item *next; // traverses all items struct menu_item *left, *right; // traverses matching items - struct item_group *group; + struct page *page; }; -struct item_group { +struct page { struct menu_item *first; struct menu_item *last; - struct item_group *prev; - struct item_group *next; + struct page *prev; + struct page *next; }; struct output { @@ -100,7 +100,7 @@ struct menu_state { struct menu_item *matchstart; struct menu_item *matchend; struct menu_item *selection; - struct item_group *groups; + struct page *pages; }; static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { @@ -115,6 +115,71 @@ static void insert(struct menu_state *state, const char *s, ssize_t n); static void match(struct menu_state *state); static size_t nextrune(struct menu_state *state, int incr); +static void append_page(struct page *page, struct page **first, struct page **last) { + if (*last) { + (*last)->next = page; + } else { + *first = page; + } + page->prev = *last; + page->next = NULL; + *last = page; +} + +static void page_items(struct menu_state *state) { + // Free existing pages + while (state->pages != NULL) { + struct page *page = state->pages; + state->pages = state->pages->next; + free(page); + } + + if (!state->matchstart) { + return; + } + + // Make new pages + if (state->vertical) { + struct page *pages_end = NULL; + struct menu_item *item = state->matchstart; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + for (int i = 1; item && i <= state->lines; i++) { + item->page = page; + page->last = item; + item = item->right; + } + append_page(page, &state->pages, &pages_end); + } + } else { + // Calculate available space + int max_width = state->width - state->inputw - state->promptw + - state->left_arrow - state->right_arrow; + + struct page *pages_end = NULL; + struct menu_item *item = state->matchstart; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + int total_width = 0; + while (item) { + total_width += item->width + 2 * state->padding; + if (total_width > max_width) { + break; + } + + item->page = page; + page->last = item; + item = item->right; + } + append_page(page, &state->pages, &pages_end); + } + } +} + int render_text(struct menu_state *state, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, @@ -186,73 +251,6 @@ void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char * pango_printf(cairo, state->font, 1, str); } -void append_group(struct item_group *group, struct item_group **first, struct item_group **last) { - if (*last) { - (*last)->next = group; - } else { - *first = group; - } - group->prev = *last; - group->next = NULL; - *last = group; -} - -void group_items(struct menu_state *state) { - // Free existing groups - struct item_group *group = state->groups; - while (group != NULL) { - struct item_group *current = group; - group = group->next; - free(current); - } - state->groups = NULL; - - if (!state->matchstart) { - return; - } - - // Make new item groups - if (state->vertical) { - struct item_group *groupend = NULL; - struct menu_item *item = state->matchstart; - while (item) { - struct item_group *group = calloc(1, sizeof(struct item_group)); - group->first = item; - - for (int i = 1; item && i <= state->lines; i++) { - item->group = group; - group->last = item; - item = item->right; - } - append_group(group, &state->groups, &groupend); - } - } else { - // Calculate available space - int max_width = state->width - state->inputw - state->promptw - - state->left_arrow - state->right_arrow; - - struct item_group *groupend = NULL; - struct menu_item *item = state->matchstart; - while (item) { - struct item_group *group = calloc(1, sizeof(struct item_group)); - group->first = item; - - int total_width = 0; - while (item) { - total_width += item->width + 2 * state->padding; - if (total_width > max_width) { - break; - } - - item->group = group; - group->last = item; - item = item->right; - } - append_group(group, &state->groups, &groupend); - } - } -} - void render_to_cairo(struct menu_state *state, cairo_t *cairo) { int width = state->width; int padding = state->padding; @@ -303,7 +301,7 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { // Draw matches vertically int y = state->line_height; struct menu_item *item; - for (item = state->selection->group->first; item != state->selection->group->last->right; item = item->right) { + for (item = state->selection->page->first; item != state->selection->page->last->right; item = item->right) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; render_vertical_item(state, cairo, item->text, @@ -325,7 +323,7 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { // Draw matches horizontally struct menu_item *item; - for (item = state->selection->group->first; item != state->selection->group->last->right; item = item->right) { + for (item = state->selection->page->first; item != state->selection->page->last->right; item = item->right) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; x = render_horizontal_item(state, cairo, item->text, @@ -335,13 +333,13 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { } // Draw left scroll indicator if necessary - if (state->selection->group->prev) { + if (state->selection->page->prev) { cairo_move_to(cairo, left_arrow_pos, 0); pango_printf(cairo, state->font, 1, "<"); } // Draw right scroll indicator if necessary - if (state->selection->group->next) { + if (state->selection->page->next) { cairo_move_to(cairo, width - state->right_arrow + padding, 0); pango_printf(cairo, state->font, 1, ">"); } @@ -656,15 +654,15 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, break; case XKB_KEY_Page_Up: case XKB_KEY_KP_Page_Up: - if (state->selection->group->prev) { - state->selection = state->selection->group->prev->first; + if (state->selection->page->prev) { + state->selection = state->selection->page->prev->first; render_frame(state); } break; case XKB_KEY_Page_Down: case XKB_KEY_KP_Page_Down: - if (state->selection->group->next) { - state->selection = state->selection->group->next->first; + if (state->selection->page->next) { + state->selection = state->selection->page->next->first; render_frame(state); } break; @@ -923,8 +921,8 @@ void match(struct menu_state *state) { state->matchend = substrend; } - group_items(state); - state->selection = state->groups->first; + page_items(state); + state->selection = state->pages->first; } size_t nextrune(struct menu_state *state, int incr) { From deab01baf12f48e30f9b6638cc4b5a16e7333b9a Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:08:18 -0500 Subject: [PATCH 13/72] Mark functions as static --- main.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/main.c b/main.c index 037161f..f7cb575 100644 --- a/main.c +++ b/main.c @@ -180,7 +180,7 @@ static void page_items(struct menu_state *state) { } } -int render_text(struct menu_state *state, cairo_t *cairo, const char *str, +static int render_text(struct menu_state *state, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding, int right_padding) { @@ -203,7 +203,7 @@ int render_text(struct menu_state *state, cairo_t *cairo, const char *str, return x + text_width + left_padding + right_padding; } -int render_horizontal_item(struct menu_state *state, cairo_t *cairo, const char *str, +static int render_horizontal_item(struct menu_state *state, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding, int right_padding) { @@ -230,7 +230,7 @@ int render_horizontal_item(struct menu_state *state, cairo_t *cairo, const char return x + text_width + left_padding + right_padding; } -void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char *str, +static void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding) { @@ -251,7 +251,7 @@ void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char * pango_printf(cairo, state->font, 1, str); } -void render_to_cairo(struct menu_state *state, cairo_t *cairo) { +static void render_to_cairo(struct menu_state *state, cairo_t *cairo) { int width = state->width; int padding = state->padding; @@ -346,7 +346,7 @@ void render_to_cairo(struct menu_state *state, cairo_t *cairo) { } } -void render_frame(struct menu_state *state) { +static void render_frame(struct menu_state *state) { cairo_surface_t *recorder = cairo_recording_surface_create( CAIRO_CONTENT_COLOR_ALPHA, NULL); cairo_t *cairo = cairo_create(recorder); @@ -415,7 +415,7 @@ static void layer_surface_closed(void *data, state->run = false; } -struct zwlr_layer_surface_v1_listener layer_surface_listener = { +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_configure, .closed = layer_surface_closed, }; @@ -434,7 +434,7 @@ static void output_name(void *data, struct wl_output *wl_output, const char *nam } } -struct wl_output_listener output_listener = { +static const struct wl_output_listener output_listener = { .geometry = noop, .mode = noop, .done = noop, @@ -466,7 +466,7 @@ static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, state->xkb_state = xkb_state_new(state->xkb_keymap); } -void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, +static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, xkb_keysym_t sym) { if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { return; @@ -723,7 +723,7 @@ void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, } } -void keyboard_repeat(struct menu_state *state) { +static void keyboard_repeat(struct menu_state *state) { keypress(state, state->repeat_key_state, state->repeat_sym); struct itimerspec spec = { 0 }; spec.it_value.tv_sec = state->repeat_period / 1000; @@ -844,7 +844,7 @@ static const struct wl_registry_listener registry_listener = { .global_remove = noop, }; -void insert(struct menu_state *state, const char *s, ssize_t n) { +static void insert(struct menu_state *state, const char *s, ssize_t n) { if (strlen(state->text) + n > sizeof state->text - 1) { return; } @@ -857,7 +857,7 @@ void insert(struct menu_state *state, const char *s, ssize_t n) { match(state); } -const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { +static const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { size_t len; for(len = strlen(sub); *s; s++) @@ -866,7 +866,7 @@ const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { return NULL; } -void append_item(struct menu_item *item, struct menu_item **first, struct menu_item **last) { +static void append_item(struct menu_item *item, struct menu_item **first, struct menu_item **last) { if (*last) { (*last)->right = item; } else { @@ -877,7 +877,7 @@ void append_item(struct menu_item *item, struct menu_item **first, struct menu_i *last = item; } -void match(struct menu_state *state) { +static void match(struct menu_state *state) { struct menu_item *lexact = NULL, *exactend = NULL; struct menu_item *lprefix = NULL, *prefixend = NULL; struct menu_item *lsubstr = NULL, *substrend = NULL; @@ -925,7 +925,7 @@ void match(struct menu_state *state) { state->selection = state->pages->first; } -size_t nextrune(struct menu_state *state, int incr) { +static size_t nextrune(struct menu_state *state, int incr) { size_t n, len; len = strlen(state->text); @@ -933,7 +933,7 @@ size_t nextrune(struct menu_state *state, int incr) { return n; } -void read_stdin(struct menu_state *state) { +static void read_stdin(struct menu_state *state) { char buf[sizeof state->text], *p; struct menu_item *item, **end; @@ -1035,7 +1035,7 @@ static void menu_create_surface(struct menu_state *state) { wl_display_roundtrip(state->display); } -bool parse_color(const char *color, uint32_t *result) { +static bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; } From e23e2154719f07a8b052669a75ca239850fd3b1f Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:14:11 -0500 Subject: [PATCH 14/72] Rename menu_item to item --- main.c | 73 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/main.c b/main.c index f7cb575..a327ae7 100644 --- a/main.c +++ b/main.c @@ -22,17 +22,20 @@ #include "pool-buffer.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" -struct menu_item { +// A menu item. +struct item { char *text; int width; - struct menu_item *next; // traverses all items - struct menu_item *left, *right; // traverses matching items + struct item *next; // traverses all items + struct item *prev_match; // previous matching item + struct item *next_match; // next matching item struct page *page; }; +// A page of menu items. struct page { - struct menu_item *first; - struct menu_item *last; + struct item *first; + struct item *last; struct page *prev; struct page *next; }; @@ -96,10 +99,10 @@ struct menu_state { bool run; bool failure; - struct menu_item *items; - struct menu_item *matchstart; - struct menu_item *matchend; - struct menu_item *selection; + struct item *items; + struct item *matchstart; + struct item *matchend; + struct item *selection; struct page *pages; }; @@ -141,7 +144,7 @@ static void page_items(struct menu_state *state) { // Make new pages if (state->vertical) { struct page *pages_end = NULL; - struct menu_item *item = state->matchstart; + struct item *item = state->matchstart; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; @@ -149,7 +152,7 @@ static void page_items(struct menu_state *state) { for (int i = 1; item && i <= state->lines; i++) { item->page = page; page->last = item; - item = item->right; + item = item->next_match; } append_page(page, &state->pages, &pages_end); } @@ -159,7 +162,7 @@ static void page_items(struct menu_state *state) { - state->left_arrow - state->right_arrow; struct page *pages_end = NULL; - struct menu_item *item = state->matchstart; + struct item *item = state->matchstart; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; @@ -173,7 +176,7 @@ static void page_items(struct menu_state *state) { item->page = page; page->last = item; - item = item->right; + item = item->next_match; } append_page(page, &state->pages, &pages_end); } @@ -300,8 +303,8 @@ static void render_to_cairo(struct menu_state *state, cairo_t *cairo) { if (state->vertical) { // Draw matches vertically int y = state->line_height; - struct menu_item *item; - for (item = state->selection->page->first; item != state->selection->page->last->right; item = item->right) { + struct item *item; + for (item = state->selection->page->first; item != state->selection->page->last->next_match; item = item->next_match) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; render_vertical_item(state, cairo, item->text, @@ -322,8 +325,8 @@ static void render_to_cairo(struct menu_state *state, cairo_t *cairo) { x += state->left_arrow; // Draw matches horizontally - struct menu_item *item; - for (item = state->selection->page->first; item != state->selection->page->last->right; item = item->right) { + struct item *item; + for (item = state->selection->page->first; item != state->selection->page->last->next_match; item = item->next_match) { uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; x = render_horizontal_item(state, cairo, item->text, @@ -632,8 +635,8 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st case XKB_KEY_KP_Left: case XKB_KEY_Up: case XKB_KEY_KP_Up: - if (state->selection && state->selection->left) { - state->selection = state->selection->left; + if (state->selection && state->selection->prev_match) { + state->selection = state->selection->prev_match; render_frame(state); } else if (state->cursor > 0) { state->cursor = nextrune(state, -1); @@ -647,8 +650,8 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st if (state->cursor < len) { state->cursor = nextrune(state, +1); render_frame(state); - } else if (state->selection && state->selection->right) { - state->selection = state->selection->right; + } else if (state->selection && state->selection->next_match) { + state->selection = state->selection->next_match; render_frame(state); } break; @@ -866,28 +869,28 @@ static const char * fstrstr(struct menu_state *state, const char *s, const char return NULL; } -static void append_item(struct menu_item *item, struct menu_item **first, struct menu_item **last) { +static void append_item(struct item *item, struct item **first, struct item **last) { if (*last) { - (*last)->right = item; + (*last)->next_match = item; } else { *first = item; } - item->left = *last; - item->right = NULL; + item->prev_match = *last; + item->next_match = NULL; *last = item; } static void match(struct menu_state *state) { - struct menu_item *lexact = NULL, *exactend = NULL; - struct menu_item *lprefix = NULL, *prefixend = NULL; - struct menu_item *lsubstr = NULL, *substrend = NULL; + struct item *lexact = NULL, *exactend = NULL; + struct item *lprefix = NULL, *prefixend = NULL; + struct item *lsubstr = NULL, *substrend = NULL; state->matchstart = NULL; state->matchend = NULL; state->selection = NULL; size_t len = strlen(state->text); - struct menu_item *item; + struct item *item; for (item = state->items; item; item = item->next) { if (!state->fstrncmp(state->text, item->text, len + 1)) { append_item(item, &lexact, &exactend); @@ -904,8 +907,8 @@ static void match(struct menu_state *state) { } if (lprefix) { if (state->matchend) { - state->matchend->right = lprefix; - lprefix->left = state->matchend; + state->matchend->next_match = lprefix; + lprefix->prev_match = state->matchend; } else { state->matchstart = lprefix; } @@ -913,8 +916,8 @@ static void match(struct menu_state *state) { } if (lsubstr) { if (state->matchend) { - state->matchend->right = lsubstr; - lsubstr->left = state->matchend; + state->matchend->next_match = lsubstr; + lsubstr->prev_match = state->matchend; } else { state->matchstart = lsubstr; } @@ -935,7 +938,7 @@ static size_t nextrune(struct menu_state *state, int incr) { static void read_stdin(struct menu_state *state) { char buf[sizeof state->text], *p; - struct menu_item *item, **end; + struct item *item, **end; for(end = &state->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { if((p = strchr(buf, '\n'))) { @@ -947,7 +950,7 @@ static void read_stdin(struct menu_state *state) { } item->text = strdup(buf); - item->next = item->left = item->right = NULL; + item->next = item->prev_match = item->next_match = NULL; cairo_t *cairo = state->current->cairo; item->width = text_width(cairo, state->font, item->text); From 086211c83c6f9110512d32633290d0a93401411a Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:29:53 -0500 Subject: [PATCH 15/72] Don't return -1 from render_horizontal_item --- main.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/main.c b/main.c index a327ae7..436e638 100644 --- a/main.c +++ b/main.c @@ -215,21 +215,17 @@ static int render_horizontal_item(struct menu_state *state, cairo_t *cairo, cons get_text_size(cairo, state->font, &text_width, &text_height, NULL, 1, str); int text_y = (state->line_height / 2.0) - (text_height / 2.0); - if (x + left_padding + text_width > width) { - return -1; - } else { - if (background) { - int bg_width = text_width + left_padding + right_padding; - cairo_set_source_u32(cairo, background); - cairo_rectangle(cairo, x, y, bg_width, height); - cairo_fill(cairo); - } - - cairo_move_to(cairo, x + left_padding, y + text_y); - cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, state->font, 1, str); + if (background) { + int bg_width = text_width + left_padding + right_padding; + cairo_set_source_u32(cairo, background); + cairo_rectangle(cairo, x, y, bg_width, height); + cairo_fill(cairo); } + cairo_move_to(cairo, x + left_padding, y + text_y); + cairo_set_source_u32(cairo, foreground); + pango_printf(cairo, state->font, 1, str); + return x + text_width + left_padding + right_padding; } From ee43ebb7832bdd3a8c47b58c08d5ccff8dda7005 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:40:18 -0500 Subject: [PATCH 16/72] Rename menu_state to menu --- main.c | 666 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 333 insertions(+), 333 deletions(-) diff --git a/main.c b/main.c index 436e638..3cd8eec 100644 --- a/main.c +++ b/main.c @@ -41,12 +41,12 @@ struct page { }; struct output { - struct menu_state *menu; + struct menu *menu; struct wl_output *output; int32_t scale; }; -struct menu_state { +struct menu { struct output *output; char *output_name; @@ -78,7 +78,7 @@ struct menu_state { int left_arrow, right_arrow; bool bottom; - int (*fstrncmp)(const char *, const char *, size_t); + int (*strncmp)(const char *, const char *, size_t); char *font; bool vertical; int lines; @@ -114,9 +114,9 @@ static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { (color >> (0*8) & 0xFF) / 255.0); } -static void insert(struct menu_state *state, const char *s, ssize_t n); -static void match(struct menu_state *state); -static size_t nextrune(struct menu_state *state, int incr); +static void insert(struct menu *menu, const char *s, ssize_t n); +static void match(struct menu *menu); +static size_t nextrune(struct menu *menu, int incr); static void append_page(struct page *page, struct page **first, struct page **last) { if (*last) { @@ -129,47 +129,47 @@ static void append_page(struct page *page, struct page **first, struct page **la *last = page; } -static void page_items(struct menu_state *state) { +static void page_items(struct menu *menu) { // Free existing pages - while (state->pages != NULL) { - struct page *page = state->pages; - state->pages = state->pages->next; + while (menu->pages != NULL) { + struct page *page = menu->pages; + menu->pages = menu->pages->next; free(page); } - if (!state->matchstart) { + if (!menu->matchstart) { return; } // Make new pages - if (state->vertical) { + if (menu->vertical) { struct page *pages_end = NULL; - struct item *item = state->matchstart; + struct item *item = menu->matchstart; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; - for (int i = 1; item && i <= state->lines; i++) { + for (int i = 1; item && i <= menu->lines; i++) { item->page = page; page->last = item; item = item->next_match; } - append_page(page, &state->pages, &pages_end); + append_page(page, &menu->pages, &pages_end); } } else { // Calculate available space - int max_width = state->width - state->inputw - state->promptw - - state->left_arrow - state->right_arrow; + int max_width = menu->width - menu->inputw - menu->promptw + - menu->left_arrow - menu->right_arrow; struct page *pages_end = NULL; - struct item *item = state->matchstart; + struct item *item = menu->matchstart; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; int total_width = 0; while (item) { - total_width += item->width + 2 * state->padding; + total_width += item->width + 2 * menu->padding; if (total_width > max_width) { break; } @@ -178,19 +178,19 @@ static void page_items(struct menu_state *state) { page->last = item; item = item->next_match; } - append_page(page, &state->pages, &pages_end); + append_page(page, &menu->pages, &pages_end); } } } -static int render_text(struct menu_state *state, cairo_t *cairo, const char *str, +static int render_text(struct menu *menu, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding, int right_padding) { int text_width, text_height; - get_text_size(cairo, state->font, &text_width, &text_height, NULL, 1, str); - int text_y = (state->line_height / 2.0) - (text_height / 2.0); + get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); + int text_y = (menu->line_height / 2.0) - (text_height / 2.0); if (background) { int bg_width = text_width + left_padding + right_padding; @@ -201,19 +201,19 @@ static int render_text(struct menu_state *state, cairo_t *cairo, const char *str cairo_move_to(cairo, x + left_padding, y + text_y); cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, state->font, 1, str); + pango_printf(cairo, menu->font, 1, str); return x + text_width + left_padding + right_padding; } -static int render_horizontal_item(struct menu_state *state, cairo_t *cairo, const char *str, +static int render_horizontal_item(struct menu *menu, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding, int right_padding) { int text_width, text_height; - get_text_size(cairo, state->font, &text_width, &text_height, NULL, 1, str); - int text_y = (state->line_height / 2.0) - (text_height / 2.0); + get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); + int text_y = (menu->line_height / 2.0) - (text_height / 2.0); if (background) { int bg_width = text_width + left_padding + right_padding; @@ -224,22 +224,22 @@ static int render_horizontal_item(struct menu_state *state, cairo_t *cairo, cons cairo_move_to(cairo, x + left_padding, y + text_y); cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, state->font, 1, str); + pango_printf(cairo, menu->font, 1, str); return x + text_width + left_padding + right_padding; } -static void render_vertical_item(struct menu_state *state, cairo_t *cairo, const char *str, +static void render_vertical_item(struct menu *menu, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, int left_padding) { int text_height; - get_text_size(cairo, state->font, NULL, &text_height, NULL, 1, str); - int text_y = (state->line_height / 2.0) - (text_height / 2.0); + get_text_size(cairo, menu->font, NULL, &text_height, NULL, 1, str); + int text_y = (menu->line_height / 2.0) - (text_height / 2.0); if (background) { - int bg_width = state->width - x; + int bg_width = menu->width - x; cairo_set_source_u32(cairo, background); cairo_rectangle(cairo, x, y, bg_width, height); cairo_fill(cairo); @@ -247,105 +247,105 @@ static void render_vertical_item(struct menu_state *state, cairo_t *cairo, const cairo_move_to(cairo, x + left_padding, y + text_y); cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, state->font, 1, str); + pango_printf(cairo, menu->font, 1, str); } -static void render_to_cairo(struct menu_state *state, cairo_t *cairo) { - int width = state->width; - int padding = state->padding; +static void render_to_cairo(struct menu *menu, cairo_t *cairo) { + int width = menu->width; + int padding = menu->padding; cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, state->background); + cairo_set_source_u32(cairo, menu->background); cairo_paint(cairo); int x = 0; // Draw prompt - if (state->prompt) { - state->promptw = render_text(state, cairo, state->prompt, - 0, 0, state->width, state->line_height, - state->promptfg, state->promptbg, + if (menu->prompt) { + menu->promptw = render_text(menu, cairo, menu->prompt, + 0, 0, menu->width, menu->line_height, + menu->promptfg, menu->promptbg, padding, padding/2); - x += state->promptw; + x += menu->promptw; } // Draw background - cairo_set_source_u32(cairo, state->background); - cairo_rectangle(cairo, x, 0, 300, state->height); + cairo_set_source_u32(cairo, menu->background); + cairo_rectangle(cairo, x, 0, 300, menu->height); cairo_fill(cairo); // Draw input - render_text(state, cairo, state->text, - x, 0, state->width, state->line_height, - state->foreground, 0, padding, padding); + render_text(menu, cairo, menu->text, + x, 0, menu->width, menu->line_height, + menu->foreground, 0, padding, padding); // Draw cursor { int cursor_width = 2; int cursor_margin = 2; int cursor_pos = x + padding - + text_width(cairo, state->font, state->text) - - text_width(cairo, state->font, &state->text[state->cursor]) + + text_width(cairo, menu->font, menu->text) + - text_width(cairo, menu->font, &menu->text[menu->cursor]) - cursor_width / 2; cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, - state->line_height - 2 * cursor_margin); + menu->line_height - 2 * cursor_margin); cairo_fill(cairo); } - if (!state->matchstart) { + if (!menu->matchstart) { return; } - if (state->vertical) { + if (menu->vertical) { // Draw matches vertically - int y = state->line_height; + int y = menu->line_height; struct item *item; - for (item = state->selection->page->first; item != state->selection->page->last->next_match; item = item->next_match) { - uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; - uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; - render_vertical_item(state, cairo, item->text, - x, y, width, state->line_height, + for (item = menu->selection->page->first; item != menu->selection->page->last->next_match; item = item->next_match) { + uint32_t bg_color = menu->selection == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->selection == item ? menu->selectionfg : menu->foreground; + render_vertical_item(menu, cairo, item->text, + x, y, width, menu->line_height, fg_color, bg_color, padding); - y += state->line_height; + y += menu->line_height; } } else { // Leave room for input - x += state->inputw; + x += menu->inputw; // Calculate scroll indicator widths - state->left_arrow = text_width(cairo, state->font, "<") + 2 * padding; - state->right_arrow = text_width(cairo, state->font, ">") + 2 * padding; + menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * padding; + menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * padding; // Remember scroll indicator position int left_arrow_pos = x + padding; - x += state->left_arrow; + x += menu->left_arrow; // Draw matches horizontally struct item *item; - for (item = state->selection->page->first; item != state->selection->page->last->next_match; item = item->next_match) { - uint32_t bg_color = state->selection == item ? state->selectionbg : state->background; - uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground; - x = render_horizontal_item(state, cairo, item->text, - x, 0, width - state->right_arrow, state->line_height, + for (item = menu->selection->page->first; item != menu->selection->page->last->next_match; item = item->next_match) { + uint32_t bg_color = menu->selection == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->selection == item ? menu->selectionfg : menu->foreground; + x = render_horizontal_item(menu, cairo, item->text, + x, 0, width - menu->right_arrow, menu->line_height, fg_color, bg_color, padding, padding); // TODO: Make sure render_horizontal_item doesn't return -1 } // Draw left scroll indicator if necessary - if (state->selection->page->prev) { + if (menu->selection->page->prev) { cairo_move_to(cairo, left_arrow_pos, 0); - pango_printf(cairo, state->font, 1, "<"); + pango_printf(cairo, menu->font, 1, "<"); } // Draw right scroll indicator if necessary - if (state->selection->page->next) { - cairo_move_to(cairo, width - state->right_arrow + padding, 0); - pango_printf(cairo, state->font, 1, ">"); + if (menu->selection->page->next) { + cairo_move_to(cairo, width - menu->right_arrow + padding, 0); + pango_printf(cairo, menu->font, 1, ">"); } } } -static void render_frame(struct menu_state *state) { +static void render_frame(struct menu *menu) { cairo_surface_t *recorder = cairo_recording_surface_create( CAIRO_CONTENT_COLOR_ALPHA, NULL); cairo_t *cairo = cairo_create(recorder); @@ -358,16 +358,16 @@ static void render_frame(struct menu_state *state) { cairo_paint(cairo); cairo_restore(cairo); - render_to_cairo(state, cairo); + render_to_cairo(menu, cairo); - int scale = state->output ? state->output->scale : 1; - state->current = get_next_buffer(state->shm, - state->buffers, state->width, state->height, scale); - if (!state->current) { + int scale = menu->output ? menu->output->scale : 1; + menu->current = get_next_buffer(menu->shm, + menu->buffers, menu->width, menu->height, scale); + if (!menu->current) { goto cleanup; } - cairo_t *shm = state->current->cairo; + cairo_t *shm = menu->current->cairo; cairo_save(shm); cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR); cairo_paint(shm); @@ -375,10 +375,10 @@ static void render_frame(struct menu_state *state) { cairo_set_source_surface(shm, recorder, 0, 0); cairo_paint(shm); - wl_surface_set_buffer_scale(state->surface, scale); - wl_surface_attach(state->surface, state->current->buffer, 0, 0); - wl_surface_damage(state->surface, 0, 0, state->width, state->height); - wl_surface_commit(state->surface); + wl_surface_set_buffer_scale(menu->surface, scale); + wl_surface_attach(menu->surface, menu->current->buffer, 0, 0); + wl_surface_damage(menu->surface, 0, 0, menu->width, menu->height); + wl_surface_commit(menu->surface); cleanup: cairo_destroy(cairo); @@ -390,8 +390,8 @@ static void noop() { static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { - struct menu_state *state = data; - state->output = wl_output_get_user_data(wl_output); + struct menu *menu = data; + menu->output = wl_output_get_user_data(wl_output); } static const struct wl_surface_listener surface_listener = { @@ -402,16 +402,16 @@ static const struct wl_surface_listener surface_listener = { static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { - struct menu_state *state = data; - state->width = width; - state->height = height; + struct menu *menu = data; + menu->width = width; + menu->height = height; zwlr_layer_surface_v1_ack_configure(surface, serial); } static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { - struct menu_state *state = data; - state->run = false; + struct menu *menu = data; + menu->run = false; } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { @@ -426,10 +426,10 @@ static void output_scale(void *data, struct wl_output *wl_output, int32_t factor static void output_name(void *data, struct wl_output *wl_output, const char *name) { struct output *output = data; - struct menu_state *state = output->menu; - char *outname = state->output_name; - if (!state->output && outname && strcmp(outname, name) == 0) { - state->output = output; + struct menu *menu = output->menu; + char *outname = menu->output_name; + if (!menu->output && outname && strcmp(outname, name) == 0) { + menu->output = output; } } @@ -444,41 +444,41 @@ static const struct wl_output_listener output_listener = { static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - struct menu_state *state = data; + struct menu *menu = data; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); - state->run = false; - state->failure = true; + menu->run = false; + menu->failure = true; return; } char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (map_shm == MAP_FAILED) { close(fd); - state->run = false; - state->failure = true; + menu->run = false; + menu->failure = true; return; } - state->xkb_keymap = xkb_keymap_new_from_string(state->xkb_context, + menu->xkb_keymap = xkb_keymap_new_from_string(menu->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_shm, size); close(fd); - state->xkb_state = xkb_state_new(state->xkb_keymap); + menu->xkb_state = xkb_state_new(menu->xkb_keymap); } -static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state, +static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, xkb_keysym_t sym) { if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { return; } - bool ctrl = xkb_state_mod_name_is_active(state->xkb_state, + bool ctrl = xkb_state_mod_name_is_active(menu->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool shift = xkb_state_mod_name_is_active(state->xkb_state, + bool shift = xkb_state_mod_name_is_active(menu->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - size_t len = strlen(state->text); + size_t len = strlen(menu->text); if (ctrl) { // Emacs-style line editing bindings @@ -529,28 +529,28 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st case XKB_KEY_k: // Delete right - state->text[state->cursor] = '\0'; - match(state); - render_frame(state); + menu->text[menu->cursor] = '\0'; + match(menu); + render_frame(menu); return; case XKB_KEY_u: // Delete left - insert(state, NULL, 0 - state->cursor); - render_frame(state); + insert(menu, NULL, 0 - menu->cursor); + render_frame(menu); return; case XKB_KEY_w: // Delete word - while (state->cursor > 0 && state->text[nextrune(state, -1)] == ' ') { - insert(state, NULL, nextrune(state, -1) - state->cursor); + while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] == ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } - while (state->cursor > 0 && state->text[nextrune(state, -1)] != ' ') { - insert(state, NULL, nextrune(state, -1) - state->cursor); + while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] != ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } - render_frame(state); + render_frame(menu); return; case XKB_KEY_Y: // Paste clipboard - if (!state->offer) { + if (!menu->offer) { return; } @@ -559,10 +559,10 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st // Pipe failed return; } - wl_data_offer_receive(state->offer, "text/plain", fds[1]); + wl_data_offer_receive(menu->offer, "text/plain", fds[1]); close(fds[1]); - wl_display_roundtrip(state->display); + wl_display_roundtrip(menu->display); while (true) { char buf[1024]; @@ -570,35 +570,35 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st if (n <= 0) { break; } - insert(state, buf, n); + insert(menu, buf, n); } close(fds[0]); - wl_data_offer_destroy(state->offer); - state->offer = NULL; - render_frame(state); + wl_data_offer_destroy(menu->offer); + menu->offer = NULL; + render_frame(menu); return; case XKB_KEY_Left: case XKB_KEY_KP_Left: // Move to beginning of word - while (state->cursor > 0 && state->text[nextrune(state, -1)] == ' ') { - state->cursor = nextrune(state, -1); + while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] == ' ') { + menu->cursor = nextrune(menu, -1); } - while (state->cursor > 0 && state->text[nextrune(state, -1)] != ' ') { - state->cursor = nextrune(state, -1); + while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] != ' ') { + menu->cursor = nextrune(menu, -1); } - render_frame(state); + render_frame(menu); return; case XKB_KEY_Right: case XKB_KEY_KP_Right: // Move to end of word - while (state->cursor < len && state->text[state->cursor] == ' ') { - state->cursor = nextrune(state, +1); + while (menu->cursor < len && menu->text[menu->cursor] == ' ') { + menu->cursor = nextrune(menu, +1); } - while (state->cursor < len && state->text[state->cursor] != ' ') { - state->cursor = nextrune(state, +1); + while (menu->cursor < len && menu->text[menu->cursor] != ' ') { + menu->cursor = nextrune(menu, +1); } - render_frame(state); + render_frame(menu); return; case XKB_KEY_Return: @@ -614,16 +614,16 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st case XKB_KEY_Return: case XKB_KEY_KP_Enter: if (shift) { - puts(state->text); + puts(menu->text); fflush(stdout); - state->run = false; + menu->run = false; } else { - char *text = state->selection ? state->selection->text - : state->text; + char *text = menu->selection ? menu->selection->text + : menu->text; puts(text); fflush(stdout); if (!ctrl) { - state->run = false; + menu->run = false; } } break; @@ -631,135 +631,135 @@ static void keypress(struct menu_state *state, enum wl_keyboard_key_state key_st case XKB_KEY_KP_Left: case XKB_KEY_Up: case XKB_KEY_KP_Up: - if (state->selection && state->selection->prev_match) { - state->selection = state->selection->prev_match; - render_frame(state); - } else if (state->cursor > 0) { - state->cursor = nextrune(state, -1); - render_frame(state); + if (menu->selection && menu->selection->prev_match) { + menu->selection = menu->selection->prev_match; + render_frame(menu); + } else if (menu->cursor > 0) { + menu->cursor = nextrune(menu, -1); + render_frame(menu); } break; case XKB_KEY_Right: case XKB_KEY_KP_Right: case XKB_KEY_Down: case XKB_KEY_KP_Down: - if (state->cursor < len) { - state->cursor = nextrune(state, +1); - render_frame(state); - } else if (state->selection && state->selection->next_match) { - state->selection = state->selection->next_match; - render_frame(state); + if (menu->cursor < len) { + menu->cursor = nextrune(menu, +1); + render_frame(menu); + } else if (menu->selection && menu->selection->next_match) { + menu->selection = menu->selection->next_match; + render_frame(menu); } break; case XKB_KEY_Page_Up: case XKB_KEY_KP_Page_Up: - if (state->selection->page->prev) { - state->selection = state->selection->page->prev->first; - render_frame(state); + if (menu->selection->page->prev) { + menu->selection = menu->selection->page->prev->first; + render_frame(menu); } break; case XKB_KEY_Page_Down: case XKB_KEY_KP_Page_Down: - if (state->selection->page->next) { - state->selection = state->selection->page->next->first; - render_frame(state); + if (menu->selection->page->next) { + menu->selection = menu->selection->page->next->first; + render_frame(menu); } break; case XKB_KEY_Home: case XKB_KEY_KP_Home: - if (state->selection == state->matchstart) { - state->cursor = 0; - render_frame(state); + if (menu->selection == menu->matchstart) { + menu->cursor = 0; + render_frame(menu); } else { - state->selection = state->matchstart; - render_frame(state); + menu->selection = menu->matchstart; + render_frame(menu); } break; case XKB_KEY_End: case XKB_KEY_KP_End: - if (state->cursor < len) { - state->cursor = len; - render_frame(state); + if (menu->cursor < len) { + menu->cursor = len; + render_frame(menu); } else { - state->selection = state->matchend; - render_frame(state); + menu->selection = menu->matchend; + render_frame(menu); } break; case XKB_KEY_BackSpace: - if (state->cursor > 0) { - insert(state, NULL, nextrune(state, -1) - state->cursor); - render_frame(state); + if (menu->cursor > 0) { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + render_frame(menu); } break; case XKB_KEY_Delete: case XKB_KEY_KP_Delete: - if (state->cursor == len) { + if (menu->cursor == len) { return; } - state->cursor = nextrune(state, +1); - insert(state, NULL, nextrune(state, -1) - state->cursor); - render_frame(state); + menu->cursor = nextrune(menu, +1); + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + render_frame(menu); break; case XKB_KEY_Tab: - if (!state->selection) { + if (!menu->selection) { return; } - state->cursor = strnlen(state->selection->text, sizeof state->text - 1); - memcpy(state->text, state->selection->text, state->cursor); - state->text[state->cursor] = '\0'; - match(state); - render_frame(state); + menu->cursor = strnlen(menu->selection->text, sizeof menu->text - 1); + memcpy(menu->text, menu->selection->text, menu->cursor); + menu->text[menu->cursor] = '\0'; + match(menu); + render_frame(menu); break; case XKB_KEY_Escape: - state->failure = true; - state->run = false; + menu->failure = true; + menu->run = false; break; default: if (xkb_keysym_to_utf8(sym, buf, 8)) { - insert(state, buf, strnlen(buf, 8)); - render_frame(state); + insert(menu, buf, strnlen(buf, 8)); + render_frame(menu); } } } -static void keyboard_repeat(struct menu_state *state) { - keypress(state, state->repeat_key_state, state->repeat_sym); +static void keyboard_repeat(struct menu *menu) { + keypress(menu, menu->repeat_key_state, menu->repeat_sym); struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = state->repeat_period / 1000; - spec.it_value.tv_nsec = (state->repeat_period % 1000) * 1000000l; - timerfd_settime(state->repeat_timer, 0, &spec, NULL); + spec.it_value.tv_sec = menu->repeat_period / 1000; + spec.it_value.tv_nsec = (menu->repeat_period % 1000) * 1000000l; + timerfd_settime(menu->repeat_timer, 0, &spec, NULL); } static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { - struct menu_state *state = data; + struct menu *menu = data; enum wl_keyboard_key_state key_state = _key_state; - xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb_state, key + 8); - keypress(state, key_state, sym); + xkb_keysym_t sym = xkb_state_key_get_one_sym(menu->xkb_state, key + 8); + keypress(menu, key_state, sym); - if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && state->repeat_period >= 0) { - state->repeat_key_state = key_state; - state->repeat_sym = sym; + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && menu->repeat_period >= 0) { + menu->repeat_key_state = key_state; + menu->repeat_sym = sym; struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = state->repeat_delay / 1000; - spec.it_value.tv_nsec = (state->repeat_delay % 1000) * 1000000l; - timerfd_settime(state->repeat_timer, 0, &spec, NULL); + spec.it_value.tv_sec = menu->repeat_delay / 1000; + spec.it_value.tv_nsec = (menu->repeat_delay % 1000) * 1000000l; + timerfd_settime(menu->repeat_timer, 0, &spec, NULL); } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { struct itimerspec spec = { 0 }; - timerfd_settime(state->repeat_timer, 0, &spec, NULL); + timerfd_settime(menu->repeat_timer, 0, &spec, NULL); } } static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { - struct menu_state *state = data; - state->repeat_delay = delay; + struct menu *menu = data; + menu->repeat_delay = delay; if (rate > 0) { - state->repeat_period = 1000 / rate; + menu->repeat_period = 1000 / rate; } else { - state->repeat_period = -1; + menu->repeat_period = -1; } } @@ -767,8 +767,8 @@ static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct menu_state *state = data; - xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched, + struct menu *menu = data; + xkb_state_update_mask(menu->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } @@ -783,10 +783,10 @@ static const struct wl_keyboard_listener keyboard_listener = { static void seat_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { - struct menu_state *state = data; + struct menu *menu = data; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { struct wl_keyboard *keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(keyboard, &keyboard_listener, state); + wl_keyboard_add_listener(keyboard, &keyboard_listener, menu); } } @@ -797,8 +797,8 @@ static const struct wl_seat_listener seat_listener = { static void data_device_selection(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) { - struct menu_state *state = data; - state->offer = offer; + struct menu *menu = data; + menu->offer = offer; } static const struct wl_data_device_listener data_device_listener = { @@ -812,26 +812,26 @@ static const struct wl_data_device_listener data_device_listener = { static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - struct menu_state *state = data; + struct menu *menu = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { - state->compositor = wl_registry_bind(registry, name, + menu->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } else if (strcmp(interface, wl_shm_interface.name) == 0) { - state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + menu->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, wl_seat_interface.name) == 0) { - state->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); - wl_seat_add_listener(state->seat, &seat_listener, state); + menu->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); + wl_seat_add_listener(menu->seat, &seat_listener, menu); } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { - state->data_device_manager = wl_registry_bind(registry, name, + menu->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - state->layer_shell = wl_registry_bind(registry, name, + menu->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct output *output = calloc(1, sizeof(struct output)); output->output = wl_registry_bind(registry, name, &wl_output_interface, 4); - output->menu = state; + output->menu = menu; output->scale = 1; wl_output_set_user_data(output->output, output); wl_output_add_listener(output->output, &output_listener, output); @@ -843,25 +843,25 @@ static const struct wl_registry_listener registry_listener = { .global_remove = noop, }; -static void insert(struct menu_state *state, const char *s, ssize_t n) { - if (strlen(state->text) + n > sizeof state->text - 1) { +static void insert(struct menu *menu, const char *s, ssize_t n) { + if (strlen(menu->text) + n > sizeof menu->text - 1) { return; } - memmove(state->text + state->cursor + n, state->text + state->cursor, - sizeof state->text - state->cursor - MAX(n, 0)); + memmove(menu->text + menu->cursor + n, menu->text + menu->cursor, + sizeof menu->text - menu->cursor - MAX(n, 0)); if (n > 0 && s != NULL) { - memcpy(state->text + state->cursor, s, n); + memcpy(menu->text + menu->cursor, s, n); } - state->cursor += n; - match(state); + menu->cursor += n; + match(menu); } -static const char * fstrstr(struct menu_state *state, const char *s, const char *sub) { - size_t len; - - for(len = strlen(sub); *s; s++) - if(!state->fstrncmp(s, sub, len)) +static const char * fstrstr(struct menu *menu, const char *s, const char *sub) { + for (size_t len = strlen(sub); *s; s++) { + if (!menu->strncmp(s, sub, len)) { return s; + } + } return NULL; } @@ -876,67 +876,67 @@ static void append_item(struct item *item, struct item **first, struct item **la *last = item; } -static void match(struct menu_state *state) { +static void match(struct menu *menu) { struct item *lexact = NULL, *exactend = NULL; struct item *lprefix = NULL, *prefixend = NULL; struct item *lsubstr = NULL, *substrend = NULL; - state->matchstart = NULL; - state->matchend = NULL; - state->selection = NULL; + menu->matchstart = NULL; + menu->matchend = NULL; + menu->selection = NULL; - size_t len = strlen(state->text); + size_t len = strlen(menu->text); struct item *item; - for (item = state->items; item; item = item->next) { - if (!state->fstrncmp(state->text, item->text, len + 1)) { + for (item = menu->items; item; item = item->next) { + if (!menu->strncmp(menu->text, item->text, len + 1)) { append_item(item, &lexact, &exactend); - } else if (!state->fstrncmp(state->text, item->text, len)) { + } else if (!menu->strncmp(menu->text, item->text, len)) { append_item(item, &lprefix, &prefixend); - } else if (fstrstr(state, item->text, state->text)) { + } else if (fstrstr(menu, item->text, menu->text)) { append_item(item, &lsubstr, &substrend); } } if (lexact) { - state->matchstart = lexact; - state->matchend = exactend; + menu->matchstart = lexact; + menu->matchend = exactend; } if (lprefix) { - if (state->matchend) { - state->matchend->next_match = lprefix; - lprefix->prev_match = state->matchend; + if (menu->matchend) { + menu->matchend->next_match = lprefix; + lprefix->prev_match = menu->matchend; } else { - state->matchstart = lprefix; + menu->matchstart = lprefix; } - state->matchend = prefixend; + menu->matchend = prefixend; } if (lsubstr) { - if (state->matchend) { - state->matchend->next_match = lsubstr; - lsubstr->prev_match = state->matchend; + if (menu->matchend) { + menu->matchend->next_match = lsubstr; + lsubstr->prev_match = menu->matchend; } else { - state->matchstart = lsubstr; + menu->matchstart = lsubstr; } - state->matchend = substrend; + menu->matchend = substrend; } - page_items(state); - state->selection = state->pages->first; + page_items(menu); + menu->selection = menu->pages->first; } -static size_t nextrune(struct menu_state *state, int incr) { +static size_t nextrune(struct menu *menu, int incr) { size_t n, len; - len = strlen(state->text); - for(n = state->cursor + incr; n < len && (state->text[n] & 0xc0) == 0x80; n += incr); + len = strlen(menu->text); + for(n = menu->cursor + incr; n < len && (menu->text[n] & 0xc0) == 0x80; n += incr); return n; } -static void read_stdin(struct menu_state *state) { - char buf[sizeof state->text], *p; +static void read_stdin(struct menu *menu) { + char buf[sizeof menu->text], *p; struct item *item, **end; - for(end = &state->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { + for(end = &menu->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { if((p = strchr(buf, '\n'))) { *p = '\0'; } @@ -948,90 +948,90 @@ static void read_stdin(struct menu_state *state) { item->text = strdup(buf); item->next = item->prev_match = item->next_match = NULL; - cairo_t *cairo = state->current->cairo; - item->width = text_width(cairo, state->font, item->text); - if (item->width > state->inputw) { - state->inputw = item->width; + cairo_t *cairo = menu->current->cairo; + item->width = text_width(cairo, menu->font, item->text); + if (item->width > menu->inputw) { + menu->inputw = item->width; } } } -static void menu_init(struct menu_state *state) { - int height = get_font_height(state->font); - state->line_height = height + 3; - state->height = state->line_height; - if (state->vertical) { - state->height += state->height * state->lines; +static void menu_init(struct menu *menu) { + int height = get_font_height(menu->font); + menu->line_height = height + 3; + menu->height = menu->line_height; + if (menu->vertical) { + menu->height += menu->height * menu->lines; } - state->padding = height / 2; + menu->padding = height / 2; - state->display = wl_display_connect(NULL); - if (!state->display) { + menu->display = wl_display_connect(NULL); + if (!menu->display) { fprintf(stderr, "wl_display_connect: %s\n", strerror(errno)); exit(EXIT_FAILURE); } - state->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!state->xkb_context) { + menu->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!menu->xkb_context) { fprintf(stderr, "xkb_context_new: %s\n", strerror(errno)); exit(EXIT_FAILURE); } - state->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); - assert(state->repeat_timer >= 0); + menu->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); + assert(menu->repeat_timer >= 0); - struct wl_registry *registry = wl_display_get_registry(state->display); - wl_registry_add_listener(registry, ®istry_listener, state); - wl_display_roundtrip(state->display); - assert(state->compositor != NULL); - assert(state->shm != NULL); - assert(state->seat != NULL); - assert(state->data_device_manager != NULL); - assert(state->layer_shell != NULL); + struct wl_registry *registry = wl_display_get_registry(menu->display); + wl_registry_add_listener(registry, ®istry_listener, menu); + wl_display_roundtrip(menu->display); + assert(menu->compositor != NULL); + assert(menu->shm != NULL); + assert(menu->seat != NULL); + assert(menu->data_device_manager != NULL); + assert(menu->layer_shell != NULL); // Get data device for seat struct wl_data_device *data_device = wl_data_device_manager_get_data_device( - state->data_device_manager, state->seat); - wl_data_device_add_listener(data_device, &data_device_listener, state); + menu->data_device_manager, menu->seat); + wl_data_device_add_listener(data_device, &data_device_listener, menu); // Second roundtrip for xdg-output - wl_display_roundtrip(state->display); + wl_display_roundtrip(menu->display); - if (state->output_name && !state->output) { - fprintf(stderr, "Output %s not found\n", state->output_name); + if (menu->output_name && !menu->output) { + fprintf(stderr, "Output %s not found\n", menu->output_name); exit(EXIT_FAILURE); } } -static void menu_create_surface(struct menu_state *state) { - state->surface = wl_compositor_create_surface(state->compositor); - wl_surface_add_listener(state->surface, &surface_listener, state); - state->layer_surface = zwlr_layer_shell_v1_get_layer_surface( - state->layer_shell, - state->surface, +static void menu_create_surface(struct menu *menu) { + menu->surface = wl_compositor_create_surface(menu->compositor); + wl_surface_add_listener(menu->surface, &surface_listener, menu); + menu->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + menu->layer_shell, + menu->surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "menu" ); - assert(state->layer_surface != NULL); + assert(menu->layer_surface != NULL); uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if (state->bottom) { + if (menu->bottom) { anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else { anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; } - zwlr_layer_surface_v1_set_anchor(state->layer_surface, anchor); - zwlr_layer_surface_v1_set_size(state->layer_surface, 0, state->height); - zwlr_layer_surface_v1_set_exclusive_zone(state->layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(state->layer_surface, true); - zwlr_layer_surface_v1_add_listener(state->layer_surface, - &layer_surface_listener, state); + zwlr_layer_surface_v1_set_anchor(menu->layer_surface, anchor); + zwlr_layer_surface_v1_set_size(menu->layer_surface, 0, menu->height); + zwlr_layer_surface_v1_set_exclusive_zone(menu->layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(menu->layer_surface, true); + zwlr_layer_surface_v1_add_listener(menu->layer_surface, + &layer_surface_listener, menu); - wl_surface_commit(state->surface); - wl_display_roundtrip(state->display); + wl_surface_commit(menu->surface); + wl_display_roundtrip(menu->display); } static bool parse_color(const char *color, uint32_t *result) { @@ -1052,8 +1052,8 @@ static bool parse_color(const char *color, uint32_t *result) { } int main(int argc, char **argv) { - struct menu_state state = { - .fstrncmp = strncmp, + struct menu menu = { + .strncmp = strncmp, .font = "monospace 10", .vertical = false, .background = 0x222222ff, @@ -1073,54 +1073,54 @@ int main(int argc, char **argv) { while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) { switch (opt) { case 'b': - state.bottom = true; + menu.bottom = true; break; case 'i': - state.fstrncmp = strncasecmp; + menu.strncmp = strncasecmp; break; case 'v': puts("wmenu " VERSION); exit(EXIT_SUCCESS); case 'f': - state.font = optarg; + menu.font = optarg; break; case 'l': - state.vertical = true; - state.lines = atoi(optarg); + menu.vertical = true; + menu.lines = atoi(optarg); break; case 'o': - state.output_name = optarg; + menu.output_name = optarg; break; case 'p': - state.prompt = optarg; + menu.prompt = optarg; break; case 'N': - if (!parse_color(optarg, &state.background)) { + if (!parse_color(optarg, &menu.background)) { fprintf(stderr, "Invalid background color: %s", optarg); } break; case 'n': - if (!parse_color(optarg, &state.foreground)) { + if (!parse_color(optarg, &menu.foreground)) { fprintf(stderr, "Invalid foreground color: %s", optarg); } break; case 'M': - if (!parse_color(optarg, &state.promptbg)) { + if (!parse_color(optarg, &menu.promptbg)) { fprintf(stderr, "Invalid prompt background color: %s", optarg); } break; case 'm': - if (!parse_color(optarg, &state.promptfg)) { + if (!parse_color(optarg, &menu.promptfg)) { fprintf(stderr, "Invalid prompt foreground color: %s", optarg); } break; case 'S': - if (!parse_color(optarg, &state.selectionbg)) { + if (!parse_color(optarg, &menu.selectionbg)) { fprintf(stderr, "Invalid selection background color: %s", optarg); } break; case 's': - if (!parse_color(optarg, &state.selectionfg)) { + if (!parse_color(optarg, &menu.selectionfg)) { fprintf(stderr, "Invalid selection foreground color: %s", optarg); } break; @@ -1135,24 +1135,24 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - menu_init(&state); - menu_create_surface(&state); - render_frame(&state); + menu_init(&menu); + menu_create_surface(&menu); + render_frame(&menu); - read_stdin(&state); - match(&state); - render_frame(&state); + read_stdin(&menu); + match(&menu); + render_frame(&menu); struct pollfd fds[] = { - { wl_display_get_fd(state.display), POLLIN }, - { state.repeat_timer, POLLIN }, + { wl_display_get_fd(menu.display), POLLIN }, + { menu.repeat_timer, POLLIN }, }; const size_t nfds = sizeof(fds) / sizeof(*fds); - while (state.run) { + while (menu.run) { errno = 0; do { - if (wl_display_flush(state.display) == -1 && errno != EAGAIN) { + if (wl_display_flush(menu.display) == -1 && errno != EAGAIN) { fprintf(stderr, "wl_display_flush: %s\n", strerror(errno)); break; } @@ -1164,19 +1164,19 @@ int main(int argc, char **argv) { } if (fds[0].revents & POLLIN) { - if (wl_display_dispatch(state.display) < 0) { - state.run = false; + if (wl_display_dispatch(menu.display) < 0) { + menu.run = false; } } if (fds[1].revents & POLLIN) { - keyboard_repeat(&state); + keyboard_repeat(&menu); } } - wl_display_disconnect(state.display); + wl_display_disconnect(menu.display); - if (state.failure) { + if (menu.failure) { return EXIT_FAILURE; } return EXIT_SUCCESS; From ce43ccfb75525aba40c2c3be82e5d9b129b047eb Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:42:11 -0500 Subject: [PATCH 17/72] Add some comments to item and page --- main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main.c b/main.c index 3cd8eec..369799c 100644 --- a/main.c +++ b/main.c @@ -29,15 +29,15 @@ struct item { struct item *next; // traverses all items struct item *prev_match; // previous matching item struct item *next_match; // next matching item - struct page *page; + struct page *page; // the page holding this item }; // A page of menu items. struct page { - struct item *first; - struct item *last; - struct page *prev; - struct page *next; + struct item *first; // first item in the page + struct item *last; // last item in the page + struct page *prev; // previous page + struct page *next; // next page }; struct output { From 48f4a1d2ed523ae8b0c0c6b97561d96a3d5ab369 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 14:50:09 -0500 Subject: [PATCH 18/72] Add comments to menu --- main.c | 97 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/main.c b/main.c index 369799c..73c4d39 100644 --- a/main.c +++ b/main.c @@ -99,11 +99,11 @@ struct menu { bool run; bool failure; - struct item *items; - struct item *matchstart; - struct item *matchend; - struct item *selection; - struct page *pages; + struct item *items; // list of all items + 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 }; static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { @@ -137,14 +137,14 @@ static void page_items(struct menu *menu) { free(page); } - if (!menu->matchstart) { + if (!menu->matches) { return; } // Make new pages if (menu->vertical) { struct page *pages_end = NULL; - struct item *item = menu->matchstart; + struct item *item = menu->matches; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; @@ -162,7 +162,7 @@ static void page_items(struct menu *menu) { - menu->left_arrow - menu->right_arrow; struct page *pages_end = NULL; - struct item *item = menu->matchstart; + struct item *item = menu->matches; while (item) { struct page *page = calloc(1, sizeof(struct page)); page->first = item; @@ -292,7 +292,7 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { cairo_fill(cairo); } - if (!menu->matchstart) { + if (!menu->matches) { return; } @@ -300,9 +300,9 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { // Draw matches vertically int y = menu->line_height; struct item *item; - for (item = menu->selection->page->first; item != menu->selection->page->last->next_match; item = item->next_match) { - uint32_t bg_color = menu->selection == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->selection == item ? menu->selectionfg : menu->foreground; + for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; render_vertical_item(menu, cairo, item->text, x, y, width, menu->line_height, fg_color, bg_color, padding); @@ -322,9 +322,9 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { // Draw matches horizontally struct item *item; - for (item = menu->selection->page->first; item != menu->selection->page->last->next_match; item = item->next_match) { - uint32_t bg_color = menu->selection == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->selection == item ? menu->selectionfg : menu->foreground; + for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; x = render_horizontal_item(menu, cairo, item->text, x, 0, width - menu->right_arrow, menu->line_height, fg_color, bg_color, padding, padding); @@ -332,13 +332,13 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { } // Draw left scroll indicator if necessary - if (menu->selection->page->prev) { + if (menu->sel->page->prev) { cairo_move_to(cairo, left_arrow_pos, 0); pango_printf(cairo, menu->font, 1, "<"); } // Draw right scroll indicator if necessary - if (menu->selection->page->next) { + if (menu->sel->page->next) { cairo_move_to(cairo, width - menu->right_arrow + padding, 0); pango_printf(cairo, menu->font, 1, ">"); } @@ -618,8 +618,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, fflush(stdout); menu->run = false; } else { - char *text = menu->selection ? menu->selection->text - : menu->text; + char *text = menu->sel ? menu->sel->text : menu->text; puts(text); fflush(stdout); if (!ctrl) { @@ -631,8 +630,8 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_KP_Left: case XKB_KEY_Up: case XKB_KEY_KP_Up: - if (menu->selection && menu->selection->prev_match) { - menu->selection = menu->selection->prev_match; + if (menu->sel && menu->sel->prev_match) { + menu->sel = menu->sel->prev_match; render_frame(menu); } else if (menu->cursor > 0) { menu->cursor = nextrune(menu, -1); @@ -646,32 +645,32 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, if (menu->cursor < len) { menu->cursor = nextrune(menu, +1); render_frame(menu); - } else if (menu->selection && menu->selection->next_match) { - menu->selection = menu->selection->next_match; + } else if (menu->sel && menu->sel->next_match) { + menu->sel = menu->sel->next_match; render_frame(menu); } break; case XKB_KEY_Page_Up: case XKB_KEY_KP_Page_Up: - if (menu->selection->page->prev) { - menu->selection = menu->selection->page->prev->first; + if (menu->sel->page->prev) { + menu->sel = menu->sel->page->prev->first; render_frame(menu); } break; case XKB_KEY_Page_Down: case XKB_KEY_KP_Page_Down: - if (menu->selection->page->next) { - menu->selection = menu->selection->page->next->first; + if (menu->sel->page->next) { + menu->sel = menu->sel->page->next->first; render_frame(menu); } break; case XKB_KEY_Home: case XKB_KEY_KP_Home: - if (menu->selection == menu->matchstart) { + if (menu->sel == menu->matches) { menu->cursor = 0; render_frame(menu); } else { - menu->selection = menu->matchstart; + menu->sel = menu->matches; render_frame(menu); } break; @@ -681,7 +680,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, menu->cursor = len; render_frame(menu); } else { - menu->selection = menu->matchend; + menu->sel = menu->matches_end; render_frame(menu); } break; @@ -701,11 +700,11 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, render_frame(menu); break; case XKB_KEY_Tab: - if (!menu->selection) { + if (!menu->sel) { return; } - menu->cursor = strnlen(menu->selection->text, sizeof menu->text - 1); - memcpy(menu->text, menu->selection->text, menu->cursor); + menu->cursor = strnlen(menu->sel->text, sizeof menu->text - 1); + memcpy(menu->text, menu->sel->text, menu->cursor); menu->text[menu->cursor] = '\0'; match(menu); render_frame(menu); @@ -880,9 +879,9 @@ static void match(struct menu *menu) { struct item *lexact = NULL, *exactend = NULL; struct item *lprefix = NULL, *prefixend = NULL; struct item *lsubstr = NULL, *substrend = NULL; - menu->matchstart = NULL; - menu->matchend = NULL; - menu->selection = NULL; + menu->matches = NULL; + menu->matches_end = NULL; + menu->sel = NULL; size_t len = strlen(menu->text); @@ -898,30 +897,30 @@ static void match(struct menu *menu) { } if (lexact) { - menu->matchstart = lexact; - menu->matchend = exactend; + menu->matches = lexact; + menu->matches_end = exactend; } if (lprefix) { - if (menu->matchend) { - menu->matchend->next_match = lprefix; - lprefix->prev_match = menu->matchend; + if (menu->matches_end) { + menu->matches_end->next_match = lprefix; + lprefix->prev_match = menu->matches_end; } else { - menu->matchstart = lprefix; + menu->matches = lprefix; } - menu->matchend = prefixend; + menu->matches_end = prefixend; } if (lsubstr) { - if (menu->matchend) { - menu->matchend->next_match = lsubstr; - lsubstr->prev_match = menu->matchend; + if (menu->matches_end) { + menu->matches_end->next_match = lsubstr; + lsubstr->prev_match = menu->matches_end; } else { - menu->matchstart = lsubstr; + menu->matches = lsubstr; } - menu->matchend = substrend; + menu->matches_end = substrend; } page_items(menu); - menu->selection = menu->pages->first; + menu->sel = menu->pages->first; } static size_t nextrune(struct menu *menu, int incr) { From 7284f5958bea340e25598562821b73db3c26ee63 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 15:03:42 -0500 Subject: [PATCH 19/72] Don't match items in insert --- main.c | 209 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 105 insertions(+), 104 deletions(-) diff --git a/main.c b/main.c index 73c4d39..fc9438b 100644 --- a/main.c +++ b/main.c @@ -106,18 +106,6 @@ struct menu { struct page *pages; // list of pages }; -static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { - cairo_set_source_rgba(cairo, - (color >> (3*8) & 0xFF) / 255.0, - (color >> (2*8) & 0xFF) / 255.0, - (color >> (1*8) & 0xFF) / 255.0, - (color >> (0*8) & 0xFF) / 255.0); -} - -static void insert(struct menu *menu, const char *s, ssize_t n); -static void match(struct menu *menu); -static size_t nextrune(struct menu *menu, int incr); - static void append_page(struct page *page, struct page **first, struct page **last) { if (*last) { (*last)->next = page; @@ -183,6 +171,102 @@ static void page_items(struct menu *menu) { } } +static const char * fstrstr(struct menu *menu, const char *s, const char *sub) { + for (size_t len = strlen(sub); *s; s++) { + if (!menu->strncmp(s, sub, len)) { + return s; + } + } + return NULL; +} + +static void append_item(struct item *item, struct item **first, struct item **last) { + if (*last) { + (*last)->next_match = item; + } else { + *first = item; + } + item->prev_match = *last; + item->next_match = NULL; + *last = item; +} + +static void match_items(struct menu *menu) { + struct item *lexact = NULL, *exactend = NULL; + struct item *lprefix = NULL, *prefixend = NULL; + struct item *lsubstr = NULL, *substrend = NULL; + menu->matches = NULL; + menu->matches_end = NULL; + menu->sel = NULL; + + size_t len = strlen(menu->text); + + struct item *item; + for (item = menu->items; item; item = item->next) { + if (!menu->strncmp(menu->text, item->text, len + 1)) { + append_item(item, &lexact, &exactend); + } else if (!menu->strncmp(menu->text, item->text, len)) { + append_item(item, &lprefix, &prefixend); + } else if (fstrstr(menu, item->text, menu->text)) { + append_item(item, &lsubstr, &substrend); + } + } + + if (lexact) { + menu->matches = lexact; + menu->matches_end = exactend; + } + if (lprefix) { + if (menu->matches_end) { + menu->matches_end->next_match = lprefix; + lprefix->prev_match = menu->matches_end; + } else { + menu->matches = lprefix; + } + menu->matches_end = prefixend; + } + if (lsubstr) { + if (menu->matches_end) { + menu->matches_end->next_match = lsubstr; + lsubstr->prev_match = menu->matches_end; + } else { + menu->matches = lsubstr; + } + menu->matches_end = substrend; + } + + page_items(menu); + menu->sel = menu->pages->first; +} + +static void insert(struct menu *menu, const char *s, ssize_t n) { + if (strlen(menu->text) + n > sizeof menu->text - 1) { + return; + } + memmove(menu->text + menu->cursor + n, menu->text + menu->cursor, + sizeof menu->text - menu->cursor - MAX(n, 0)); + if (n > 0 && s != NULL) { + memcpy(menu->text + menu->cursor, s, n); + } + menu->cursor += n; +} + +static size_t nextrune(struct menu *menu, int incr) { + size_t n, len; + + len = strlen(menu->text); + for(n = menu->cursor + incr; n < len && (menu->text[n] & 0xc0) == 0x80; n += incr); + return n; +} + +static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { + cairo_set_source_rgba(cairo, + (color >> (3*8) & 0xFF) / 255.0, + (color >> (2*8) & 0xFF) / 255.0, + (color >> (1*8) & 0xFF) / 255.0, + (color >> (0*8) & 0xFF) / 255.0); +} + static int render_text(struct menu *menu, cairo_t *cairo, const char *str, int x, int y, int width, int height, uint32_t foreground, uint32_t background, @@ -530,12 +614,13 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_k: // Delete right menu->text[menu->cursor] = '\0'; - match(menu); + match_items(menu); render_frame(menu); return; case XKB_KEY_u: // Delete left insert(menu, NULL, 0 - menu->cursor); + match_items(menu); render_frame(menu); return; case XKB_KEY_w: @@ -546,6 +631,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] != ' ') { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } + match_items(menu); render_frame(menu); return; case XKB_KEY_Y: @@ -576,6 +662,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, wl_data_offer_destroy(menu->offer); menu->offer = NULL; + match_items(menu); render_frame(menu); return; case XKB_KEY_Left: @@ -687,6 +774,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_BackSpace: if (menu->cursor > 0) { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + match_items(menu); render_frame(menu); } break; @@ -697,6 +785,7 @@ static void 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_frame(menu); break; case XKB_KEY_Tab: @@ -706,7 +795,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, menu->cursor = strnlen(menu->sel->text, sizeof menu->text - 1); memcpy(menu->text, menu->sel->text, menu->cursor); menu->text[menu->cursor] = '\0'; - match(menu); + match_items(menu); render_frame(menu); break; case XKB_KEY_Escape: @@ -716,6 +805,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, default: if (xkb_keysym_to_utf8(sym, buf, 8)) { insert(menu, buf, strnlen(buf, 8)); + match_items(menu); render_frame(menu); } } @@ -842,95 +932,6 @@ static const struct wl_registry_listener registry_listener = { .global_remove = noop, }; -static void insert(struct menu *menu, const char *s, ssize_t n) { - if (strlen(menu->text) + n > sizeof menu->text - 1) { - return; - } - memmove(menu->text + menu->cursor + n, menu->text + menu->cursor, - sizeof menu->text - menu->cursor - MAX(n, 0)); - if (n > 0 && s != NULL) { - memcpy(menu->text + menu->cursor, s, n); - } - menu->cursor += n; - match(menu); -} - -static const char * fstrstr(struct menu *menu, const char *s, const char *sub) { - for (size_t len = strlen(sub); *s; s++) { - if (!menu->strncmp(s, sub, len)) { - return s; - } - } - return NULL; -} - -static void append_item(struct item *item, struct item **first, struct item **last) { - if (*last) { - (*last)->next_match = item; - } else { - *first = item; - } - item->prev_match = *last; - item->next_match = NULL; - *last = item; -} - -static void match(struct menu *menu) { - struct item *lexact = NULL, *exactend = NULL; - struct item *lprefix = NULL, *prefixend = NULL; - struct item *lsubstr = NULL, *substrend = NULL; - menu->matches = NULL; - menu->matches_end = NULL; - menu->sel = NULL; - - size_t len = strlen(menu->text); - - struct item *item; - for (item = menu->items; item; item = item->next) { - if (!menu->strncmp(menu->text, item->text, len + 1)) { - append_item(item, &lexact, &exactend); - } else if (!menu->strncmp(menu->text, item->text, len)) { - append_item(item, &lprefix, &prefixend); - } else if (fstrstr(menu, item->text, menu->text)) { - append_item(item, &lsubstr, &substrend); - } - } - - if (lexact) { - menu->matches = lexact; - menu->matches_end = exactend; - } - if (lprefix) { - if (menu->matches_end) { - menu->matches_end->next_match = lprefix; - lprefix->prev_match = menu->matches_end; - } else { - menu->matches = lprefix; - } - menu->matches_end = prefixend; - } - if (lsubstr) { - if (menu->matches_end) { - menu->matches_end->next_match = lsubstr; - lsubstr->prev_match = menu->matches_end; - } else { - menu->matches = lsubstr; - } - menu->matches_end = substrend; - } - - page_items(menu); - menu->sel = menu->pages->first; -} - -static size_t nextrune(struct menu *menu, int incr) { - size_t n, len; - - len = strlen(menu->text); - for(n = menu->cursor + incr; n < len && (menu->text[n] & 0xc0) == 0x80; n += incr); - return n; -} - static void read_stdin(struct menu *menu) { char buf[sizeof menu->text], *p; struct item *item, **end; @@ -1139,7 +1140,7 @@ int main(int argc, char **argv) { render_frame(&menu); read_stdin(&menu); - match(&menu); + match_items(&menu); render_frame(&menu); struct pollfd fds[] = { From da25fbfb275e5f9ffa7fff3ff1cd1d6f16c652b1 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 15:05:37 -0500 Subject: [PATCH 20/72] Don't set selection if there are no pages --- main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index fc9438b..9e67f7f 100644 --- a/main.c +++ b/main.c @@ -236,7 +236,9 @@ static void match_items(struct menu *menu) { } page_items(menu); - menu->sel = menu->pages->first; + if (menu->pages) { + menu->sel = menu->pages->first; + } } static void insert(struct menu *menu, const char *s, ssize_t n) { From 628a5d82eedb93401e71f08c8ad976294ba6453e Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 16:14:04 -0500 Subject: [PATCH 21/72] Refactor rendering code --- main.c | 157 ++++++++++++++++++++++----------------------------------- 1 file changed, 61 insertions(+), 96 deletions(-) diff --git a/main.c b/main.c index 9e67f7f..88095ab 100644 --- a/main.c +++ b/main.c @@ -171,7 +171,7 @@ static void page_items(struct menu *menu) { } } -static const char * fstrstr(struct menu *menu, const char *s, const char *sub) { +static const char *fstrstr(struct menu *menu, const char *s, const char *sub) { for (size_t len = strlen(sub); *s; s++) { if (!menu->strncmp(s, sub, len)) { return s; @@ -261,6 +261,30 @@ static size_t nextrune(struct menu *menu, int incr) { return n; } +// Calculate text widths. +static void calc_widths(struct menu *menu) { + cairo_t *cairo = menu->current->cairo; + + // Calculate prompt width + if (menu->prompt) { + menu->promptw = text_width(cairo, menu->font, menu->prompt); + } else { + menu->promptw = 0; + } + + // Calculate scroll indicator widths + menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * menu->padding; + 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) { + item->width = text_width(cairo, menu->font, item->text); + if (item->width > menu->inputw) { + menu->inputw = item->width; + } + } +} + static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { cairo_set_source_rgba(cairo, (color >> (3*8) & 0xFF) / 255.0, @@ -270,76 +294,47 @@ static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { } static int render_text(struct menu *menu, cairo_t *cairo, const char *str, - int x, int y, int width, int height, - uint32_t foreground, uint32_t background, + int x, int y, int width, uint32_t bg_color, uint32_t fg_color, int left_padding, int right_padding) { int text_width, text_height; get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); int text_y = (menu->line_height / 2.0) - (text_height / 2.0); - if (background) { - int bg_width = text_width + left_padding + right_padding; - cairo_set_source_u32(cairo, background); - cairo_rectangle(cairo, x, y, bg_width, height); + if (width == 0) { + width = text_width + left_padding + right_padding; + } + if (bg_color) { + cairo_set_source_u32(cairo, bg_color); + cairo_rectangle(cairo, x, y, width, menu->line_height); cairo_fill(cairo); } - cairo_move_to(cairo, x + left_padding, y + text_y); - cairo_set_source_u32(cairo, foreground); + cairo_set_source_u32(cairo, fg_color); pango_printf(cairo, menu->font, 1, str); - return x + text_width + left_padding + right_padding; + return width; } -static int render_horizontal_item(struct menu *menu, cairo_t *cairo, const char *str, - int x, int y, int width, int height, - uint32_t foreground, uint32_t background, - int left_padding, int right_padding) { +static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - int text_width, text_height; - get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); - int text_y = (menu->line_height / 2.0) - (text_height / 2.0); - - if (background) { - int bg_width = text_width + left_padding + right_padding; - cairo_set_source_u32(cairo, background); - cairo_rectangle(cairo, x, y, bg_width, height); - cairo_fill(cairo); - } - - cairo_move_to(cairo, x + left_padding, y + text_y); - cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, menu->font, 1, str); - - return x + text_width + left_padding + right_padding; + return render_text(menu, cairo, item->text, x, 0, 0, + bg_color, fg_color, menu->padding, menu->padding); } -static void render_vertical_item(struct menu *menu, cairo_t *cairo, const char *str, - int x, int y, int width, int height, - uint32_t foreground, uint32_t background, - int left_padding) { +static void render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - int text_height; - get_text_size(cairo, menu->font, NULL, &text_height, NULL, 1, str); - int text_y = (menu->line_height / 2.0) - (text_height / 2.0); - - if (background) { - int bg_width = menu->width - x; - cairo_set_source_u32(cairo, background); - cairo_rectangle(cairo, x, y, bg_width, height); - cairo_fill(cairo); - } - - cairo_move_to(cairo, x + left_padding, y + text_y); - cairo_set_source_u32(cairo, foreground); - pango_printf(cairo, menu->font, 1, str); + render_text(menu, cairo, item->text, x, y, menu->width - x, + bg_color, fg_color, menu->padding, 0); + return; } static void render_to_cairo(struct menu *menu, cairo_t *cairo) { - int width = menu->width; - int padding = menu->padding; - + // Draw background cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_u32(cairo, menu->background); cairo_paint(cairo); @@ -348,28 +343,19 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { // Draw prompt if (menu->prompt) { - menu->promptw = render_text(menu, cairo, menu->prompt, - 0, 0, menu->width, menu->line_height, - menu->promptfg, menu->promptbg, - padding, padding/2); - x += menu->promptw; + x += render_text(menu, cairo, menu->prompt, 0, 0, 0, + menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); } - // Draw background - cairo_set_source_u32(cairo, menu->background); - cairo_rectangle(cairo, x, 0, 300, menu->height); - cairo_fill(cairo); - // Draw input - render_text(menu, cairo, menu->text, - x, 0, menu->width, menu->line_height, - menu->foreground, 0, padding, padding); + render_text(menu, cairo, menu->text, x, 0, 0, + 0, menu->foreground, menu->padding, menu->padding); // Draw cursor { - int cursor_width = 2; - int cursor_margin = 2; - int cursor_pos = x + padding + const int cursor_width = 2; + const int cursor_margin = 2; + int cursor_pos = x + menu->padding + text_width(cairo, menu->font, menu->text) - text_width(cairo, menu->font, &menu->text[menu->cursor]) - cursor_width / 2; @@ -382,50 +368,34 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { return; } + // Draw matches if (menu->vertical) { // Draw matches vertically int y = menu->line_height; struct item *item; for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - render_vertical_item(menu, cairo, item->text, - x, y, width, menu->line_height, - fg_color, bg_color, padding); + render_vertical_item(menu, cairo, item, x, y); y += menu->line_height; } } else { - // Leave room for input - x += menu->inputw; - - // Calculate scroll indicator widths - menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * padding; - menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * padding; - - // Remember scroll indicator position - int left_arrow_pos = x + padding; - x += menu->left_arrow; + // Leave room for input and left arrow + x += menu->inputw + menu->left_arrow; // Draw matches horizontally struct item *item; for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - x = render_horizontal_item(menu, cairo, item->text, - x, 0, width - menu->right_arrow, menu->line_height, - fg_color, bg_color, padding, padding); - // TODO: Make sure render_horizontal_item doesn't return -1 + x += render_horizontal_item(menu, cairo, item, x); } // Draw left scroll indicator if necessary if (menu->sel->page->prev) { - cairo_move_to(cairo, left_arrow_pos, 0); + cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); pango_printf(cairo, menu->font, 1, "<"); } // Draw right scroll indicator if necessary if (menu->sel->page->next) { - cairo_move_to(cairo, width - menu->right_arrow + padding, 0); + cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); pango_printf(cairo, menu->font, 1, ">"); } } @@ -949,12 +919,6 @@ static void read_stdin(struct menu *menu) { item->text = strdup(buf); item->next = item->prev_match = item->next_match = NULL; - - cairo_t *cairo = menu->current->cairo; - item->width = text_width(cairo, menu->font, item->text); - if (item->width > menu->inputw) { - menu->inputw = item->width; - } } } @@ -1142,6 +1106,7 @@ int main(int argc, char **argv) { render_frame(&menu); read_stdin(&menu); + calc_widths(&menu); match_items(&menu); render_frame(&menu); From c6025455ec7a5a2ce0367097d465c7188759886b Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 16:31:04 -0500 Subject: [PATCH 22/72] Add functions to render pages of items --- main.c | 65 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/main.c b/main.c index 88095ab..c288e99 100644 --- a/main.c +++ b/main.c @@ -316,6 +316,7 @@ static int render_text(struct menu *menu, cairo_t *cairo, const char *str, return width; } +// Renders a single menu item horizontally. static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; @@ -324,13 +325,41 @@ static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item bg_color, fg_color, menu->padding, menu->padding); } -static void render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { +// Renders a single menu item vertically. +static int render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; render_text(menu, cairo, item->text, x, y, menu->width - x, bg_color, fg_color, menu->padding, 0); - return; + return menu->line_height; +} + +// Renders a page of menu items horizontally. +static void render_horizontal_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw + menu->inputw + menu->left_arrow; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + x += render_horizontal_item(menu, cairo, item, x); + } + + // Draw left and right scroll indicators if necessary + if (page->prev) { + cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); + pango_printf(cairo, menu->font, 1, "<"); + } + if (page->next) { + cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); + pango_printf(cairo, menu->font, 1, ">"); + } +} + +// Renders a page of menu items vertically. +static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw; + int y = menu->line_height; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + y += render_vertical_item(menu, cairo, item, x, y); + } } static void render_to_cairo(struct menu *menu, cairo_t *cairo) { @@ -364,40 +393,14 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { cairo_fill(cairo); } - if (!menu->matches) { + if (!menu->sel) { return; } - // Draw matches if (menu->vertical) { - // Draw matches vertically - int y = menu->line_height; - struct item *item; - for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { - render_vertical_item(menu, cairo, item, x, y); - y += menu->line_height; - } + render_vertical_page(menu, cairo, menu->sel->page); } else { - // Leave room for input and left arrow - x += menu->inputw + menu->left_arrow; - - // Draw matches horizontally - struct item *item; - for (item = menu->sel->page->first; item != menu->sel->page->last->next_match; item = item->next_match) { - x += render_horizontal_item(menu, cairo, item, x); - } - - // Draw left scroll indicator if necessary - if (menu->sel->page->prev) { - cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); - pango_printf(cairo, menu->font, 1, "<"); - } - - // Draw right scroll indicator if necessary - if (menu->sel->page->next) { - cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); - pango_printf(cairo, menu->font, 1, ">"); - } + render_horizontal_page(menu, cairo, menu->sel->page); } } From f9167689dcd4ab0231bacdcdf0173747fb6a738b Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 16:31:41 -0500 Subject: [PATCH 23/72] Check if selection is not null before dereferencing --- main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index c288e99..94de88f 100644 --- a/main.c +++ b/main.c @@ -714,14 +714,14 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, break; case XKB_KEY_Page_Up: case XKB_KEY_KP_Page_Up: - if (menu->sel->page->prev) { + if (menu->sel && menu->sel->page->prev) { menu->sel = menu->sel->page->prev->first; render_frame(menu); } break; case XKB_KEY_Page_Down: case XKB_KEY_KP_Page_Down: - if (menu->sel->page->next) { + if (menu->sel && menu->sel->page->next) { menu->sel = menu->sel->page->next->first; render_frame(menu); } From 96b3c0ef266494e6a8c0a91d1726f8dcde95d55d Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 26 Feb 2024 16:44:23 -0500 Subject: [PATCH 24/72] Add more rendering functions --- main.c | 118 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/main.c b/main.c index 94de88f..5f9969e 100644 --- a/main.c +++ b/main.c @@ -87,7 +87,7 @@ struct menu { uint32_t promptbg, promptfg; uint32_t selectionbg, selectionfg; - char text[BUFSIZ]; + char input[BUFSIZ]; size_t cursor; int repeat_timer; @@ -199,15 +199,15 @@ static void match_items(struct menu *menu) { menu->matches_end = NULL; menu->sel = NULL; - size_t len = strlen(menu->text); + size_t len = strlen(menu->input); struct item *item; for (item = menu->items; item; item = item->next) { - if (!menu->strncmp(menu->text, item->text, len + 1)) { + if (!menu->strncmp(menu->input, item->text, len + 1)) { append_item(item, &lexact, &exactend); - } else if (!menu->strncmp(menu->text, item->text, len)) { + } else if (!menu->strncmp(menu->input, item->text, len)) { append_item(item, &lprefix, &prefixend); - } else if (fstrstr(menu, item->text, menu->text)) { + } else if (fstrstr(menu, item->text, menu->input)) { append_item(item, &lsubstr, &substrend); } } @@ -242,13 +242,13 @@ static void match_items(struct menu *menu) { } static void insert(struct menu *menu, const char *s, ssize_t n) { - if (strlen(menu->text) + n > sizeof menu->text - 1) { + if (strlen(menu->input) + n > sizeof menu->input - 1) { return; } - memmove(menu->text + menu->cursor + n, menu->text + menu->cursor, - sizeof menu->text - menu->cursor - MAX(n, 0)); + memmove(menu->input + menu->cursor + n, menu->input + menu->cursor, + sizeof menu->input - menu->cursor - MAX(n, 0)); if (n > 0 && s != NULL) { - memcpy(menu->text + menu->cursor, s, n); + memcpy(menu->input + menu->cursor, s, n); } menu->cursor += n; } @@ -256,8 +256,8 @@ static void insert(struct menu *menu, const char *s, ssize_t n) { static size_t nextrune(struct menu *menu, int incr) { size_t n, len; - len = strlen(menu->text); - for(n = menu->cursor + incr; n < len && (menu->text[n] & 0xc0) == 0x80; n += incr); + len = strlen(menu->input); + for(n = menu->cursor + incr; n < len && (menu->input[n] & 0xc0) == 0x80; n += incr); return n; } @@ -267,7 +267,7 @@ static void calc_widths(struct menu *menu) { // Calculate prompt width if (menu->prompt) { - menu->promptw = text_width(cairo, menu->font, menu->prompt); + menu->promptw = text_width(cairo, menu->font, menu->prompt) + menu->padding + menu->padding/2; } else { menu->promptw = 0; } @@ -293,6 +293,7 @@ static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { (color >> (0*8) & 0xFF) / 255.0); } +// Renders text to cairo. static int render_text(struct menu *menu, cairo_t *cairo, const char *str, int x, int y, int width, uint32_t bg_color, uint32_t fg_color, int left_padding, int right_padding) { @@ -316,6 +317,34 @@ static int render_text(struct menu *menu, cairo_t *cairo, const char *str, return width; } +// Renders the prompt message. +static void render_prompt(struct menu *menu, cairo_t *cairo) { + if (!menu->prompt) { + return; + } + render_text(menu, cairo, menu->prompt, 0, 0, 0, + menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); +} + +// 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->foreground, menu->padding, menu->padding); +} + +// Renders a cursor for the input field. +static void render_cursor(struct menu *menu, cairo_t *cairo) { + const int cursor_width = 2; + const int cursor_margin = 2; + int cursor_pos = menu->promptw + menu->padding + + text_width(cairo, menu->font, menu->input) + - text_width(cairo, menu->font, &menu->input[menu->cursor]) + - cursor_width / 2; + cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, + menu->line_height - 2 * cursor_margin); + cairo_fill(cairo); +} + // Renders a single menu item horizontally. static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; @@ -362,41 +391,22 @@ static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page } } -static void render_to_cairo(struct menu *menu, cairo_t *cairo) { - // Draw background +// Renders the menu to cairo. +static void render_menu(struct menu *menu, cairo_t *cairo) { + // Render background cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_u32(cairo, menu->background); cairo_paint(cairo); - int x = 0; - - // Draw prompt - if (menu->prompt) { - x += render_text(menu, cairo, menu->prompt, 0, 0, 0, - menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); - } - - // Draw input - render_text(menu, cairo, menu->text, x, 0, 0, - 0, menu->foreground, menu->padding, menu->padding); - - // Draw cursor - { - const int cursor_width = 2; - const int cursor_margin = 2; - int cursor_pos = x + menu->padding - + text_width(cairo, menu->font, menu->text) - - text_width(cairo, menu->font, &menu->text[menu->cursor]) - - cursor_width / 2; - cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, - menu->line_height - 2 * cursor_margin); - cairo_fill(cairo); - } + // Render prompt and input + render_prompt(menu, cairo); + render_input(menu, cairo); + render_cursor(menu, cairo); + // Render selected page if (!menu->sel) { return; } - // Draw matches if (menu->vertical) { render_vertical_page(menu, cairo, menu->sel->page); } else { @@ -417,7 +427,7 @@ static void render_frame(struct menu *menu) { cairo_paint(cairo); cairo_restore(cairo); - render_to_cairo(menu, cairo); + render_menu(menu, cairo); int scale = menu->output ? menu->output->scale : 1; menu->current = get_next_buffer(menu->shm, @@ -537,7 +547,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - size_t len = strlen(menu->text); + size_t len = strlen(menu->input); if (ctrl) { // Emacs-style line editing bindings @@ -588,7 +598,7 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_k: // Delete right - menu->text[menu->cursor] = '\0'; + menu->input[menu->cursor] = '\0'; match_items(menu); render_frame(menu); return; @@ -600,10 +610,10 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; case XKB_KEY_w: // Delete word - while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] == ' ') { + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } - while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] != ' ') { + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { insert(menu, NULL, nextrune(menu, -1) - menu->cursor); } match_items(menu); @@ -643,10 +653,10 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_Left: case XKB_KEY_KP_Left: // Move to beginning of word - while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] == ' ') { + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { menu->cursor = nextrune(menu, -1); } - while (menu->cursor > 0 && menu->text[nextrune(menu, -1)] != ' ') { + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { menu->cursor = nextrune(menu, -1); } render_frame(menu); @@ -654,10 +664,10 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_Right: case XKB_KEY_KP_Right: // Move to end of word - while (menu->cursor < len && menu->text[menu->cursor] == ' ') { + while (menu->cursor < len && menu->input[menu->cursor] == ' ') { menu->cursor = nextrune(menu, +1); } - while (menu->cursor < len && menu->text[menu->cursor] != ' ') { + while (menu->cursor < len && menu->input[menu->cursor] != ' ') { menu->cursor = nextrune(menu, +1); } render_frame(menu); @@ -676,11 +686,11 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, case XKB_KEY_Return: case XKB_KEY_KP_Enter: if (shift) { - puts(menu->text); + puts(menu->input); fflush(stdout); menu->run = false; } else { - char *text = menu->sel ? menu->sel->text : menu->text; + char *text = menu->sel ? menu->sel->text : menu->input; puts(text); fflush(stdout); if (!ctrl) { @@ -767,9 +777,9 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, if (!menu->sel) { return; } - menu->cursor = strnlen(menu->sel->text, sizeof menu->text - 1); - memcpy(menu->text, menu->sel->text, menu->cursor); - menu->text[menu->cursor] = '\0'; + menu->cursor = strnlen(menu->sel->text, sizeof menu->input - 1); + memcpy(menu->input, menu->sel->text, menu->cursor); + menu->input[menu->cursor] = '\0'; match_items(menu); render_frame(menu); break; @@ -908,7 +918,7 @@ static const struct wl_registry_listener registry_listener = { }; static void read_stdin(struct menu *menu) { - char buf[sizeof menu->text], *p; + char buf[sizeof menu->input], *p; struct item *item, **end; for(end = &menu->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { From 04dfc063795c56a5fdb3096bf8fe0b4123c3c28d Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Tue, 27 Feb 2024 00:10:34 -0500 Subject: [PATCH 25/72] Add token matching like dmenu This change ports dmenu's token matching of space-separated input to wmenu to match the behaviour of dmenu, with a slightly more verbose but hopefully more readable implementation. --- main.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index 5f9969e..fca5254 100644 --- a/main.c +++ b/main.c @@ -195,19 +195,49 @@ static void match_items(struct menu *menu) { struct item *lexact = NULL, *exactend = NULL; struct item *lprefix = NULL, *prefixend = NULL; struct item *lsubstr = NULL, *substrend = NULL; + char buf[sizeof menu->input], *tok; + char **tokv = NULL; + int i, tokc = 0; + size_t tok_len; menu->matches = NULL; menu->matches_end = NULL; menu->sel = NULL; - size_t len = strlen(menu->input); + size_t text_len = strlen(menu->input); + + /* tokenize text by space for matching the tokens individually */ + strcpy(buf, menu->input); + tok = strtok(buf, " "); + while (tok) { + tokv = realloc(tokv, (tokc + 1) * sizeof *tokv); + if (!tokv) { + fprintf(stderr, "could not realloc %zu bytes", + (tokc + 1) * sizeof *tokv); + exit(EXIT_FAILURE); + } + tokv[tokc] = tok; + tokc++; + tok = strtok(NULL, " "); + } + tok_len = tokc ? strlen(tokv[0]) : 0; struct item *item; for (item = menu->items; item; item = item->next) { - if (!menu->strncmp(menu->input, item->text, len + 1)) { + for (i = 0; i < tokc; i++) { + if (!fstrstr(menu, item->text, tokv[i])) { + /* token does not match */ + break; + } + } + if (i != tokc) { + /* not all tokens match */ + continue; + } + if (!tokc || !menu->strncmp(menu->input, item->text, text_len + 1)) { append_item(item, &lexact, &exactend); - } else if (!menu->strncmp(menu->input, item->text, len)) { + } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { append_item(item, &lprefix, &prefixend); - } else if (fstrstr(menu, item->text, menu->input)) { + } else { append_item(item, &lsubstr, &substrend); } } From c37c3fe38e77398fba65d169c664f569fbab9760 Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Tue, 27 Feb 2024 00:49:58 -0500 Subject: [PATCH 26/72] Add dmenu's Meta (Alt) keybindings This change adds dmenu's mixture of Emacs+vim-style Meta keybindings. Also 'Page_Up' and 'Page_Down' were deprecated in upstream xkbcommon, so replace them with the new 'Prior' and 'Next' names respectively. --- docs/wmenu.1.scd | 24 +++++++++++++++ main.c | 80 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 5fbf50e..56d3865 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -148,3 +148,27 @@ arrow keys, page up, page down, home, and end. |[ *C-w* :[ Delete word left +|[ *M-b* +:[ Move cursor to the start of the current word. + +|[ *M-f* +:[ Move cursor to the end of the current word. + +|[ *M-g* +:[ Home + +|[ *M-G* +:[ End + +|[ *M-h* +:[ Up + +|[ *M-j* +:[ Page down + +|[ *M-k* +:[ Page up + +|[ *M-l* +:[ Down + diff --git a/main.c b/main.c index fca5254..4a831ca 100644 --- a/main.c +++ b/main.c @@ -291,6 +291,27 @@ static size_t nextrune(struct menu *menu, int incr) { return n; } +static void movewordedge(struct menu *menu, int dir) { + if (dir < 0) { + // Move to beginning of word + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { + menu->cursor = nextrune(menu, -1); + } + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { + menu->cursor = nextrune(menu, -1); + } + } else { + // Move to end of word + size_t len = strlen(menu->input); + while (menu->cursor < len && menu->input[menu->cursor] == ' ') { + menu->cursor = nextrune(menu, +1); + } + while (menu->cursor < len && menu->input[menu->cursor] != ' ') { + menu->cursor = nextrune(menu, +1); + } + } +} + // Calculate text widths. static void calc_widths(struct menu *menu) { cairo_t *cairo = menu->current->cairo; @@ -573,6 +594,9 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, bool ctrl = xkb_state_mod_name_is_active(menu->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + bool meta = xkb_state_mod_name_is_active(menu->xkb_state, + XKB_MOD_NAME_ALT, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); bool shift = xkb_state_mod_name_is_active(menu->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); @@ -682,24 +706,12 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; case XKB_KEY_Left: case XKB_KEY_KP_Left: - // Move to beginning of word - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { - menu->cursor = nextrune(menu, -1); - } - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { - menu->cursor = nextrune(menu, -1); - } + movewordedge(menu, -1); render_frame(menu); return; case XKB_KEY_Right: case XKB_KEY_KP_Right: - // Move to end of word - while (menu->cursor < len && menu->input[menu->cursor] == ' ') { - menu->cursor = nextrune(menu, +1); - } - while (menu->cursor < len && menu->input[menu->cursor] != ' ') { - menu->cursor = nextrune(menu, +1); - } + movewordedge(menu, +1); render_frame(menu); return; @@ -709,6 +721,38 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, default: return; } + } else if (meta) { + // Emacs-style line editing bindings + switch (sym) { + case XKB_KEY_b: + movewordedge(menu, -1); + render_frame(menu); + return; + case XKB_KEY_f: + movewordedge(menu, +1); + render_frame(menu); + return; + case XKB_KEY_g: + sym = XKB_KEY_Home; + break; + case XKB_KEY_G: + sym = XKB_KEY_End; + break; + case XKB_KEY_h: + sym = XKB_KEY_Up; + break; + case XKB_KEY_j: + sym = XKB_KEY_Next; + break; + case XKB_KEY_k: + sym = XKB_KEY_Prior; + break; + case XKB_KEY_l: + sym = XKB_KEY_Down; + break; + default: + return; + } } char buf[8]; @@ -752,15 +796,15 @@ static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, render_frame(menu); } break; - case XKB_KEY_Page_Up: - case XKB_KEY_KP_Page_Up: + case XKB_KEY_Prior: + case XKB_KEY_KP_Prior: if (menu->sel && menu->sel->page->prev) { menu->sel = menu->sel->page->prev->first; render_frame(menu); } break; - case XKB_KEY_Page_Down: - case XKB_KEY_KP_Page_Down: + case XKB_KEY_Next: + case XKB_KEY_KP_Next: if (menu->sel && menu->sel->page->next) { menu->sel = menu->sel->page->next->first; render_frame(menu); From 8bcad262a4d047140767d9467ac5526bb768a95e Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 08:07:16 -0500 Subject: [PATCH 27/72] Simplify movewordedge --- main.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/main.c b/main.c index 4a831ca..29eb060 100644 --- a/main.c +++ b/main.c @@ -291,24 +291,14 @@ static size_t nextrune(struct menu *menu, int incr) { return n; } +// Move the cursor to the beginning or end of the word, skipping over any preceding whitespace. static void movewordedge(struct menu *menu, int dir) { - if (dir < 0) { - // Move to beginning of word - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { - menu->cursor = nextrune(menu, -1); - } - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { - menu->cursor = nextrune(menu, -1); - } - } else { - // Move to end of word - size_t len = strlen(menu->input); - while (menu->cursor < len && menu->input[menu->cursor] == ' ') { - menu->cursor = nextrune(menu, +1); - } - while (menu->cursor < len && menu->input[menu->cursor] != ' ') { - menu->cursor = nextrune(menu, +1); - } + size_t len = strlen(menu->input); + while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] == ' ') { + menu->cursor = nextrune(menu, dir); + } + while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] != ' ') { + menu->cursor = nextrune(menu, dir); } } From f609762c4e39a4542c0b9b55965d245a79ed91d7 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 08:17:38 -0500 Subject: [PATCH 28/72] Add C-Y keybinding to docs --- docs/wmenu.1.scd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 56d3865..f9b5d4f 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -148,6 +148,9 @@ arrow keys, page up, page down, home, and end. |[ *C-w* :[ Delete word left +|[ *C-Y* +:[ Paste from Wayland clipboard + |[ *M-b* :[ Move cursor to the start of the current word. From 906f7ccee81881754c023db0389c8510c0218ec8 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 08:37:17 -0500 Subject: [PATCH 29/72] Improve formatting of docs --- docs/wmenu.1.scd | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index f9b5d4f..0117aa1 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -92,86 +92,86 @@ arrow keys, page up, page down, home, and end. Move cursor to the end of the current word. |[ *C-a* -:[ Home +:< Home |[ *C-b* -:[ Left +:< Left |[ *C-c* -:[ Escape +:< Escape |[ *C-d* -:[ Delete +:< Delete |[ *C-e* -:[ End +:< End |[ *C-f* -:[ Right +:< Right |[ *C-g* -:[ Escape +:< Escape |[ *C-[* -:[ Escape +:< Escape |[ *C-h* -:[ Backspace +:< Backspace |[ *C-i* -:[ Tab +:< Tab |[ *C-j* -:[ Return +:< Return |[ *C-J* -:[ Shift-Return +:< Shift-Return |[ *C-k* -:[ Delete line right +:< Delete line right |[ *C-m* -:[ Return +:< Return |[ *C-M* -:[ Shift-Return +:< Shift-Return |[ *C-n* -:[ Down +:< Down |[ *C-p* -:[ Up +:< Up |[ *C-u* -:[ Delete line left +:< Delete line left |[ *C-w* -:[ Delete word left +:< Delete word left |[ *C-Y* -:[ Paste from Wayland clipboard +:< Paste from Wayland clipboard |[ *M-b* -:[ Move cursor to the start of the current word. +:< Move cursor to the start of the current word |[ *M-f* -:[ Move cursor to the end of the current word. +:< Move cursor to the end of the current word |[ *M-g* -:[ Home +:< Home |[ *M-G* -:[ End +:< End |[ *M-h* -:[ Up +:< Up |[ *M-j* -:[ Page down +:< Page down |[ *M-k* -:[ Page up +:< Page up |[ *M-l* -:[ Down +:< Down From 18895cd72b25654ca0f727dc60480b53fed2c904 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 08:49:09 -0500 Subject: [PATCH 30/72] Remove unused includes --- pango.c | 2 -- pango.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/pango.c b/pango.c index 122b050..390e263 100644 --- a/pango.c +++ b/pango.c @@ -1,8 +1,6 @@ #include #include -#include #include -#include #include #include #include diff --git a/pango.h b/pango.h index 563ac84..556b02b 100644 --- a/pango.h +++ b/pango.h @@ -1,8 +1,6 @@ #ifndef DMENU_PANGO_H #define DMENU_PANGO_H -#include #include -#include #include #include From 1104e8e51b366e7804b94365f8d6a84752693538 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 08:50:29 -0500 Subject: [PATCH 31/72] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 173bef4..27b06f0 100644 --- a/LICENSE +++ b/LICENSE @@ -11,7 +11,7 @@ MIT/X Consortium License © 2014-2020 Hiltjo Posthuma © 2015-2019 Quentin Rameau © 2018-2019 Henrik Nyman -© 2022 adnano +© 2022 adnano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), From e8782db9c840e42827dafa4ec1ca9849f91b9b59 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 11:23:12 -0500 Subject: [PATCH 32/72] Move menu and rendering logic into separate files --- main.c | 1030 ++++--------------------------------------------- menu.c | 631 ++++++++++++++++++++++++++++++ menu.h | 102 +++++ meson.build | 2 + pango.c | 4 +- pango.h | 4 +- pool-buffer.c | 1 + pool-buffer.h | 5 + render.c | 199 ++++++++++ render.h | 9 + 10 files changed, 1024 insertions(+), 963 deletions(-) create mode 100644 menu.c create mode 100644 menu.h create mode 100644 render.c create mode 100644 render.h diff --git a/main.c b/main.c index 29eb060..e74a07c 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,5 @@ #define _POSIX_C_SOURCE 200809L #include -#include -#include #include #include #include @@ -18,482 +16,10 @@ #include #include -#include "pango.h" -#include "pool-buffer.h" +#include "menu.h" +#include "render.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" -// 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 -}; - -// A page of menu items. -struct page { - struct item *first; // first item in the page - struct item *last; // last item in the page - struct page *prev; // previous page - struct page *next; // next page -}; - -struct output { - struct menu *menu; - struct wl_output *output; - int32_t scale; -}; - -struct menu { - struct output *output; - char *output_name; - - struct wl_display *display; - struct wl_compositor *compositor; - struct wl_shm *shm; - struct wl_seat *seat; - struct wl_data_device_manager *data_device_manager; - struct zwlr_layer_shell_v1 *layer_shell; - - struct wl_surface *surface; - struct zwlr_layer_surface_v1 *layer_surface; - - struct xkb_context *xkb_context; - struct xkb_keymap *xkb_keymap; - struct xkb_state *xkb_state; - - struct wl_data_offer *offer; - - struct pool_buffer buffers[2]; - struct pool_buffer *current; - - int width; - int height; - int line_height; - int padding; - int inputw; - int promptw; - int left_arrow, right_arrow; - - bool bottom; - int (*strncmp)(const char *, const char *, size_t); - char *font; - bool vertical; - int lines; - char *prompt; - uint32_t background, foreground; - uint32_t promptbg, promptfg; - uint32_t selectionbg, selectionfg; - - char input[BUFSIZ]; - size_t cursor; - - int repeat_timer; - int repeat_delay; - int repeat_period; - enum wl_keyboard_key_state repeat_key_state; - xkb_keysym_t repeat_sym; - - bool run; - bool failure; - - struct item *items; // list of all items - 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 -}; - -static void append_page(struct page *page, struct page **first, struct page **last) { - if (*last) { - (*last)->next = page; - } else { - *first = page; - } - page->prev = *last; - page->next = NULL; - *last = page; -} - -static void page_items(struct menu *menu) { - // Free existing pages - while (menu->pages != NULL) { - struct page *page = menu->pages; - menu->pages = menu->pages->next; - free(page); - } - - if (!menu->matches) { - return; - } - - // Make new pages - if (menu->vertical) { - struct page *pages_end = NULL; - struct item *item = menu->matches; - while (item) { - struct page *page = calloc(1, sizeof(struct page)); - page->first = item; - - for (int i = 1; item && i <= menu->lines; i++) { - item->page = page; - page->last = item; - item = item->next_match; - } - append_page(page, &menu->pages, &pages_end); - } - } else { - // Calculate available space - int max_width = menu->width - menu->inputw - menu->promptw - - menu->left_arrow - menu->right_arrow; - - struct page *pages_end = NULL; - struct item *item = menu->matches; - while (item) { - struct page *page = calloc(1, sizeof(struct page)); - page->first = item; - - int total_width = 0; - while (item) { - total_width += item->width + 2 * menu->padding; - if (total_width > max_width) { - break; - } - - item->page = page; - page->last = item; - item = item->next_match; - } - append_page(page, &menu->pages, &pages_end); - } - } -} - -static const char *fstrstr(struct menu *menu, const char *s, const char *sub) { - for (size_t len = strlen(sub); *s; s++) { - if (!menu->strncmp(s, sub, len)) { - return s; - } - } - return NULL; -} - -static void append_item(struct item *item, struct item **first, struct item **last) { - if (*last) { - (*last)->next_match = item; - } else { - *first = item; - } - item->prev_match = *last; - item->next_match = NULL; - *last = item; -} - -static void match_items(struct menu *menu) { - struct item *lexact = NULL, *exactend = NULL; - struct item *lprefix = NULL, *prefixend = NULL; - struct item *lsubstr = NULL, *substrend = NULL; - char buf[sizeof menu->input], *tok; - char **tokv = NULL; - int i, tokc = 0; - size_t tok_len; - menu->matches = NULL; - menu->matches_end = NULL; - menu->sel = NULL; - - size_t text_len = strlen(menu->input); - - /* tokenize text by space for matching the tokens individually */ - strcpy(buf, menu->input); - tok = strtok(buf, " "); - while (tok) { - tokv = realloc(tokv, (tokc + 1) * sizeof *tokv); - if (!tokv) { - fprintf(stderr, "could not realloc %zu bytes", - (tokc + 1) * sizeof *tokv); - exit(EXIT_FAILURE); - } - tokv[tokc] = tok; - tokc++; - tok = strtok(NULL, " "); - } - tok_len = tokc ? strlen(tokv[0]) : 0; - - 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 */ - break; - } - } - if (i != tokc) { - /* not all tokens match */ - continue; - } - if (!tokc || !menu->strncmp(menu->input, item->text, text_len + 1)) { - append_item(item, &lexact, &exactend); - } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { - append_item(item, &lprefix, &prefixend); - } else { - append_item(item, &lsubstr, &substrend); - } - } - - if (lexact) { - menu->matches = lexact; - menu->matches_end = exactend; - } - if (lprefix) { - if (menu->matches_end) { - menu->matches_end->next_match = lprefix; - lprefix->prev_match = menu->matches_end; - } else { - menu->matches = lprefix; - } - menu->matches_end = prefixend; - } - if (lsubstr) { - if (menu->matches_end) { - menu->matches_end->next_match = lsubstr; - lsubstr->prev_match = menu->matches_end; - } else { - menu->matches = lsubstr; - } - menu->matches_end = substrend; - } - - page_items(menu); - if (menu->pages) { - menu->sel = menu->pages->first; - } -} - -static void insert(struct menu *menu, const char *s, ssize_t n) { - if (strlen(menu->input) + n > sizeof menu->input - 1) { - return; - } - memmove(menu->input + menu->cursor + n, menu->input + menu->cursor, - sizeof menu->input - menu->cursor - MAX(n, 0)); - if (n > 0 && s != NULL) { - memcpy(menu->input + menu->cursor, s, n); - } - menu->cursor += n; -} - -static size_t nextrune(struct menu *menu, int incr) { - size_t n, len; - - len = strlen(menu->input); - for(n = menu->cursor + incr; n < len && (menu->input[n] & 0xc0) == 0x80; n += incr); - return n; -} - -// Move the cursor to the beginning or end of the word, skipping over any preceding whitespace. -static void movewordedge(struct menu *menu, int dir) { - size_t len = strlen(menu->input); - while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] == ' ') { - menu->cursor = nextrune(menu, dir); - } - while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] != ' ') { - menu->cursor = nextrune(menu, dir); - } -} - -// Calculate text widths. -static void calc_widths(struct menu *menu) { - cairo_t *cairo = menu->current->cairo; - - // Calculate prompt width - if (menu->prompt) { - menu->promptw = text_width(cairo, menu->font, menu->prompt) + menu->padding + menu->padding/2; - } else { - menu->promptw = 0; - } - - // Calculate scroll indicator widths - menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * menu->padding; - 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) { - item->width = text_width(cairo, menu->font, item->text); - if (item->width > menu->inputw) { - menu->inputw = item->width; - } - } -} - -static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { - cairo_set_source_rgba(cairo, - (color >> (3*8) & 0xFF) / 255.0, - (color >> (2*8) & 0xFF) / 255.0, - (color >> (1*8) & 0xFF) / 255.0, - (color >> (0*8) & 0xFF) / 255.0); -} - -// Renders text to cairo. -static int render_text(struct menu *menu, cairo_t *cairo, const char *str, - int x, int y, int width, uint32_t bg_color, uint32_t fg_color, - int left_padding, int right_padding) { - - int text_width, text_height; - get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); - int text_y = (menu->line_height / 2.0) - (text_height / 2.0); - - if (width == 0) { - width = text_width + left_padding + right_padding; - } - if (bg_color) { - cairo_set_source_u32(cairo, bg_color); - cairo_rectangle(cairo, x, y, width, menu->line_height); - cairo_fill(cairo); - } - cairo_move_to(cairo, x + left_padding, y + text_y); - cairo_set_source_u32(cairo, fg_color); - pango_printf(cairo, menu->font, 1, str); - - return width; -} - -// Renders the prompt message. -static void render_prompt(struct menu *menu, cairo_t *cairo) { - if (!menu->prompt) { - return; - } - render_text(menu, cairo, menu->prompt, 0, 0, 0, - menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); -} - -// 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->foreground, menu->padding, menu->padding); -} - -// Renders a cursor for the input field. -static void render_cursor(struct menu *menu, cairo_t *cairo) { - const int cursor_width = 2; - const int cursor_margin = 2; - int cursor_pos = menu->promptw + menu->padding - + text_width(cairo, menu->font, menu->input) - - text_width(cairo, menu->font, &menu->input[menu->cursor]) - - cursor_width / 2; - cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, - menu->line_height - 2 * cursor_margin); - cairo_fill(cairo); -} - -// Renders a single menu item horizontally. -static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - - return render_text(menu, cairo, item->text, x, 0, 0, - bg_color, fg_color, menu->padding, menu->padding); -} - -// Renders a single menu item vertically. -static int render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; - - render_text(menu, cairo, item->text, x, y, menu->width - x, - bg_color, fg_color, menu->padding, 0); - return menu->line_height; -} - -// Renders a page of menu items horizontally. -static void render_horizontal_page(struct menu *menu, cairo_t *cairo, struct page *page) { - int x = menu->promptw + menu->inputw + menu->left_arrow; - for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { - x += render_horizontal_item(menu, cairo, item, x); - } - - // Draw left and right scroll indicators if necessary - if (page->prev) { - cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); - pango_printf(cairo, menu->font, 1, "<"); - } - if (page->next) { - cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); - pango_printf(cairo, menu->font, 1, ">"); - } -} - -// Renders a page of menu items vertically. -static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page *page) { - int x = menu->promptw; - int y = menu->line_height; - for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { - y += render_vertical_item(menu, cairo, item, x, y); - } -} - -// Renders the menu to cairo. -static void render_menu(struct menu *menu, cairo_t *cairo) { - // Render background - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, menu->background); - cairo_paint(cairo); - - // Render prompt and input - render_prompt(menu, cairo); - render_input(menu, cairo); - render_cursor(menu, cairo); - - // Render selected page - if (!menu->sel) { - return; - } - if (menu->vertical) { - render_vertical_page(menu, cairo, menu->sel->page); - } else { - render_horizontal_page(menu, cairo, menu->sel->page); - } -} - -static void render_frame(struct menu *menu) { - 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_menu(menu, cairo); - - int scale = menu->output ? menu->output->scale : 1; - menu->current = get_next_buffer(menu->shm, - menu->buffers, menu->width, menu->height, scale); - if (!menu->current) { - goto cleanup; - } - - cairo_t *shm = menu->current->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); - - wl_surface_set_buffer_scale(menu->surface, scale); - wl_surface_attach(menu->surface, menu->current->buffer, 0, 0); - wl_surface_damage(menu->surface, 0, 0, menu->width, menu->height); - wl_surface_commit(menu->surface); - -cleanup: - cairo_destroy(cairo); -} - static void noop() { // Do nothing } @@ -521,7 +47,7 @@ static void layer_surface_configure(void *data, static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { struct menu *menu = data; - menu->run = false; + menu->exit = true; } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { @@ -554,359 +80,68 @@ static const struct wl_output_listener output_listener = { static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - struct menu *menu = data; - if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { - close(fd); - menu->run = false; - menu->failure = true; - return; - } + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + struct keyboard *keyboard = data; + + // TODO: MAP_PRIVATE vs MAP_SHARED char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - if (map_shm == MAP_FAILED) { - close(fd); - menu->run = false; - menu->failure = true; - return; - } - menu->xkb_keymap = xkb_keymap_new_from_string(menu->xkb_context, + assert (map_shm != MAP_FAILED); + + struct xkb_keymap *keymap = xkb_keymap_new_from_string(keyboard->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_shm, size); close(fd); - menu->xkb_state = xkb_state_new(menu->xkb_keymap); + + keyboard->xkb_state = xkb_state_new(keymap); } -static void keypress(struct menu *menu, enum wl_keyboard_key_state key_state, - xkb_keysym_t sym) { - if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { - return; - } - - bool ctrl = xkb_state_mod_name_is_active(menu->xkb_state, - XKB_MOD_NAME_CTRL, - XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool meta = xkb_state_mod_name_is_active(menu->xkb_state, - XKB_MOD_NAME_ALT, - XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool shift = xkb_state_mod_name_is_active(menu->xkb_state, - XKB_MOD_NAME_SHIFT, - XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - - size_t len = strlen(menu->input); - - if (ctrl) { - // Emacs-style line editing bindings - switch (sym) { - case XKB_KEY_a: - sym = XKB_KEY_Home; - break; - case XKB_KEY_b: - sym = XKB_KEY_Left; - break; - case XKB_KEY_c: - sym = XKB_KEY_Escape; - break; - case XKB_KEY_d: - sym = XKB_KEY_Delete; - break; - case XKB_KEY_e: - sym = XKB_KEY_End; - break; - case XKB_KEY_f: - sym = XKB_KEY_Right; - break; - case XKB_KEY_g: - sym = XKB_KEY_Escape; - break; - case XKB_KEY_bracketleft: - sym = XKB_KEY_Escape; - break; - case XKB_KEY_h: - sym = XKB_KEY_BackSpace; - break; - case XKB_KEY_i: - sym = XKB_KEY_Tab; - break; - case XKB_KEY_j: - case XKB_KEY_J: - case XKB_KEY_m: - case XKB_KEY_M: - sym = XKB_KEY_Return; - ctrl = false; - break; - case XKB_KEY_n: - sym = XKB_KEY_Down; - break; - case XKB_KEY_p: - sym = XKB_KEY_Up; - break; - - case XKB_KEY_k: - // Delete right - menu->input[menu->cursor] = '\0'; - match_items(menu); - render_frame(menu); - return; - case XKB_KEY_u: - // Delete left - insert(menu, NULL, 0 - menu->cursor); - match_items(menu); - render_frame(menu); - return; - case XKB_KEY_w: - // Delete word - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { - insert(menu, NULL, nextrune(menu, -1) - menu->cursor); - } - while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { - insert(menu, NULL, nextrune(menu, -1) - menu->cursor); - } - match_items(menu); - render_frame(menu); - return; - case XKB_KEY_Y: - // Paste clipboard - if (!menu->offer) { - return; - } - - int fds[2]; - if (pipe(fds) == -1) { - // Pipe failed - return; - } - wl_data_offer_receive(menu->offer, "text/plain", fds[1]); - close(fds[1]); - - wl_display_roundtrip(menu->display); - - while (true) { - char buf[1024]; - ssize_t n = read(fds[0], buf, sizeof(buf)); - if (n <= 0) { - break; - } - insert(menu, buf, n); - } - close(fds[0]); - - wl_data_offer_destroy(menu->offer); - menu->offer = NULL; - match_items(menu); - render_frame(menu); - return; - case XKB_KEY_Left: - case XKB_KEY_KP_Left: - movewordedge(menu, -1); - render_frame(menu); - return; - case XKB_KEY_Right: - case XKB_KEY_KP_Right: - movewordedge(menu, +1); - render_frame(menu); - return; - - case XKB_KEY_Return: - case XKB_KEY_KP_Enter: - break; - default: - return; - } - } else if (meta) { - // Emacs-style line editing bindings - switch (sym) { - case XKB_KEY_b: - movewordedge(menu, -1); - render_frame(menu); - return; - case XKB_KEY_f: - movewordedge(menu, +1); - render_frame(menu); - return; - case XKB_KEY_g: - sym = XKB_KEY_Home; - break; - case XKB_KEY_G: - sym = XKB_KEY_End; - break; - case XKB_KEY_h: - sym = XKB_KEY_Up; - break; - case XKB_KEY_j: - sym = XKB_KEY_Next; - break; - case XKB_KEY_k: - sym = XKB_KEY_Prior; - break; - case XKB_KEY_l: - sym = XKB_KEY_Down; - break; - default: - return; - } - } - - char buf[8]; - switch (sym) { - case XKB_KEY_Return: - case XKB_KEY_KP_Enter: - if (shift) { - puts(menu->input); - fflush(stdout); - menu->run = false; - } else { - char *text = menu->sel ? menu->sel->text : menu->input; - puts(text); - fflush(stdout); - if (!ctrl) { - menu->run = false; - } - } - break; - case XKB_KEY_Left: - case XKB_KEY_KP_Left: - case XKB_KEY_Up: - case XKB_KEY_KP_Up: - if (menu->sel && menu->sel->prev_match) { - menu->sel = menu->sel->prev_match; - render_frame(menu); - } else if (menu->cursor > 0) { - menu->cursor = nextrune(menu, -1); - render_frame(menu); - } - break; - case XKB_KEY_Right: - case XKB_KEY_KP_Right: - case XKB_KEY_Down: - case XKB_KEY_KP_Down: - if (menu->cursor < len) { - menu->cursor = nextrune(menu, +1); - render_frame(menu); - } else if (menu->sel && menu->sel->next_match) { - menu->sel = menu->sel->next_match; - render_frame(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_frame(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_frame(menu); - } - break; - case XKB_KEY_Home: - case XKB_KEY_KP_Home: - if (menu->sel == menu->matches) { - menu->cursor = 0; - render_frame(menu); - } else { - menu->sel = menu->matches; - render_frame(menu); - } - break; - case XKB_KEY_End: - case XKB_KEY_KP_End: - if (menu->cursor < len) { - menu->cursor = len; - render_frame(menu); - } else { - menu->sel = menu->matches_end; - render_frame(menu); - } - break; - case XKB_KEY_BackSpace: - if (menu->cursor > 0) { - insert(menu, NULL, nextrune(menu, -1) - menu->cursor); - match_items(menu); - render_frame(menu); - } - break; - case XKB_KEY_Delete: - case XKB_KEY_KP_Delete: - if (menu->cursor == len) { - return; - } - menu->cursor = nextrune(menu, +1); - insert(menu, NULL, nextrune(menu, -1) - menu->cursor); - match_items(menu); - render_frame(menu); - break; - case XKB_KEY_Tab: - if (!menu->sel) { - return; - } - menu->cursor = strnlen(menu->sel->text, sizeof menu->input - 1); - memcpy(menu->input, menu->sel->text, menu->cursor); - menu->input[menu->cursor] = '\0'; - match_items(menu); - render_frame(menu); - break; - case XKB_KEY_Escape: - menu->failure = true; - menu->run = false; - break; - default: - if (xkb_keysym_to_utf8(sym, buf, 8)) { - insert(menu, buf, strnlen(buf, 8)); - match_items(menu); - render_frame(menu); - } - } -} - -static void keyboard_repeat(struct menu *menu) { - keypress(menu, menu->repeat_key_state, menu->repeat_sym); +static void keyboard_repeat(struct keyboard *keyboard) { + menu_keypress(keyboard->menu, keyboard->repeat_key_state, keyboard->repeat_sym); struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = menu->repeat_period / 1000; - spec.it_value.tv_nsec = (menu->repeat_period % 1000) * 1000000l; - timerfd_settime(menu->repeat_timer, 0, &spec, NULL); + spec.it_value.tv_sec = keyboard->repeat_period / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_period % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); } static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { - struct menu *menu = data; + struct keyboard *keyboard = data; enum wl_keyboard_key_state key_state = _key_state; - xkb_keysym_t sym = xkb_state_key_get_one_sym(menu->xkb_state, key + 8); - keypress(menu, key_state, sym); + xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->xkb_state, key + 8); + menu_keypress(keyboard->menu, key_state, sym); - if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && menu->repeat_period >= 0) { - menu->repeat_key_state = key_state; - menu->repeat_sym = sym; + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && keyboard->repeat_period >= 0) { + keyboard->repeat_key_state = key_state; + keyboard->repeat_sym = sym; struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = menu->repeat_delay / 1000; - spec.it_value.tv_nsec = (menu->repeat_delay % 1000) * 1000000l; - timerfd_settime(menu->repeat_timer, 0, &spec, NULL); + spec.it_value.tv_sec = keyboard->repeat_delay / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_delay % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { struct itimerspec spec = { 0 }; - timerfd_settime(menu->repeat_timer, 0, &spec, NULL); + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); } } static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { - struct menu *menu = data; - menu->repeat_delay = delay; + struct keyboard *keyboard = data; + keyboard->repeat_delay = delay; if (rate > 0) { - menu->repeat_period = 1000 / rate; + keyboard->repeat_period = 1000 / rate; } else { - menu->repeat_period = -1; + keyboard->repeat_period = -1; } } -static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, +static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct menu *menu = data; - xkb_state_update_mask(menu->xkb_state, mods_depressed, mods_latched, + struct keyboard *keyboard = data; + xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } @@ -924,7 +159,7 @@ static void seat_capabilities(void *data, struct wl_seat *seat, struct menu *menu = data; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { struct wl_keyboard *keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(keyboard, &keyboard_listener, menu); + wl_keyboard_add_listener(keyboard, &keyboard_listener, menu->keyboard); } } @@ -981,48 +216,21 @@ static const struct wl_registry_listener registry_listener = { .global_remove = noop, }; -static void read_stdin(struct menu *menu) { - char buf[sizeof menu->input], *p; - struct item *item, **end; - - for(end = &menu->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { - if((p = strchr(buf, '\n'))) { - *p = '\0'; - } - item = malloc(sizeof *item); - if (!item) { - return; - } - - item->text = strdup(buf); - item->next = item->prev_match = item->next_match = NULL; - } +static void keyboard_init(struct keyboard *keyboard, struct menu *menu) { + keyboard->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + assert(keyboard->xkb_context != NULL); + keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); + assert(keyboard->repeat_timer >= 0); + keyboard->menu = menu; } -static void menu_init(struct menu *menu) { - int height = get_font_height(menu->font); - menu->line_height = height + 3; - menu->height = menu->line_height; - if (menu->vertical) { - menu->height += menu->height * menu->lines; - } - menu->padding = height / 2; - +static void create_surface(struct menu *menu) { menu->display = wl_display_connect(NULL); if (!menu->display) { - fprintf(stderr, "wl_display_connect: %s\n", strerror(errno)); + fprintf(stderr, "Failed to connect to display.\n"); exit(EXIT_FAILURE); } - menu->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!menu->xkb_context) { - fprintf(stderr, "xkb_context_new: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - menu->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); - assert(menu->repeat_timer >= 0); - struct wl_registry *registry = wl_display_get_registry(menu->display); wl_registry_add_listener(registry, ®istry_listener, menu); wl_display_roundtrip(menu->display); @@ -1044,19 +252,18 @@ static void menu_init(struct menu *menu) { fprintf(stderr, "Output %s not found\n", menu->output_name); exit(EXIT_FAILURE); } -} -static void menu_create_surface(struct menu *menu) { menu->surface = wl_compositor_create_surface(menu->compositor); wl_surface_add_listener(menu->surface, &surface_listener, menu); - menu->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + + struct zwlr_layer_surface_v1 *layer_surface = zwlr_layer_shell_v1_get_layer_surface( menu->layer_shell, menu->surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "menu" ); - assert(menu->layer_surface != NULL); + assert(layer_surface != NULL); uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; @@ -1066,137 +273,40 @@ static void menu_create_surface(struct menu *menu) { anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; } - zwlr_layer_surface_v1_set_anchor(menu->layer_surface, anchor); - zwlr_layer_surface_v1_set_size(menu->layer_surface, 0, menu->height); - zwlr_layer_surface_v1_set_exclusive_zone(menu->layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(menu->layer_surface, true); - zwlr_layer_surface_v1_add_listener(menu->layer_surface, - &layer_surface_listener, menu); + zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); + zwlr_layer_surface_v1_set_size(layer_surface, 0, menu->height); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, true); + zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, menu); wl_surface_commit(menu->surface); wl_display_roundtrip(menu->display); } -static bool parse_color(const char *color, uint32_t *result) { - if (color[0] == '#') { - ++color; - } - size_t len = strlen(color); - if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) { - return false; - } - char *ptr; - uint32_t parsed = (uint32_t)strtoul(color, &ptr, 16); - if (*ptr != '\0') { - return false; - } - *result = len == 6 ? ((parsed << 8) | 0xFF) : parsed; - return true; -} +int main(int argc, char *argv[]) { + struct menu *menu = calloc(1, sizeof(struct menu)); + menu_init(menu, argc, argv); -int main(int argc, char **argv) { - struct menu menu = { - .strncmp = strncmp, - .font = "monospace 10", - .vertical = false, - .background = 0x222222ff, - .foreground = 0xbbbbbbff, - .promptbg = 0x005577ff, - .promptfg = 0xeeeeeeff, - .selectionbg = 0x005577ff, - .selectionfg = 0xeeeeeeff, - .run = true, - }; + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard_init(keyboard, menu); + menu->keyboard = keyboard; - const char *usage = - "Usage: wmenu [-biv] [-f font] [-l lines] [-o output] [-p prompt]\n" - "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n"; + create_surface(menu); + render_menu(menu); - int opt; - while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) { - switch (opt) { - case 'b': - menu.bottom = true; - break; - case 'i': - menu.strncmp = strncasecmp; - break; - case 'v': - puts("wmenu " VERSION); - exit(EXIT_SUCCESS); - case 'f': - menu.font = optarg; - break; - case 'l': - menu.vertical = true; - menu.lines = atoi(optarg); - break; - case 'o': - menu.output_name = optarg; - break; - case 'p': - menu.prompt = optarg; - break; - case 'N': - if (!parse_color(optarg, &menu.background)) { - fprintf(stderr, "Invalid background color: %s", optarg); - } - break; - case 'n': - if (!parse_color(optarg, &menu.foreground)) { - fprintf(stderr, "Invalid foreground color: %s", optarg); - } - break; - case 'M': - if (!parse_color(optarg, &menu.promptbg)) { - fprintf(stderr, "Invalid prompt background color: %s", optarg); - } - break; - case 'm': - if (!parse_color(optarg, &menu.promptfg)) { - fprintf(stderr, "Invalid prompt foreground color: %s", optarg); - } - break; - case 'S': - if (!parse_color(optarg, &menu.selectionbg)) { - fprintf(stderr, "Invalid selection background color: %s", optarg); - } - break; - case 's': - if (!parse_color(optarg, &menu.selectionfg)) { - fprintf(stderr, "Invalid selection foreground color: %s", optarg); - } - break; - default: - fprintf(stderr, "%s", usage); - exit(EXIT_FAILURE); - } - } - - if (optind < argc) { - fprintf(stderr, "%s", usage); - exit(EXIT_FAILURE); - } - - menu_init(&menu); - menu_create_surface(&menu); - render_frame(&menu); - - read_stdin(&menu); - calc_widths(&menu); - match_items(&menu); - render_frame(&menu); + read_menu_items(menu); + render_menu(menu); struct pollfd fds[] = { - { wl_display_get_fd(menu.display), POLLIN }, - { menu.repeat_timer, POLLIN }, + { wl_display_get_fd(menu->display), POLLIN }, + { keyboard->repeat_timer, POLLIN }, }; const size_t nfds = sizeof(fds) / sizeof(*fds); - while (menu.run) { + while (!menu->exit) { errno = 0; do { - if (wl_display_flush(menu.display) == -1 && errno != EAGAIN) { + if (wl_display_flush(menu->display) == -1 && errno != EAGAIN) { fprintf(stderr, "wl_display_flush: %s\n", strerror(errno)); break; } @@ -1208,19 +318,19 @@ int main(int argc, char **argv) { } if (fds[0].revents & POLLIN) { - if (wl_display_dispatch(menu.display) < 0) { - menu.run = false; + if (wl_display_dispatch(menu->display) < 0) { + menu->exit = true; } } if (fds[1].revents & POLLIN) { - keyboard_repeat(&menu); + keyboard_repeat(keyboard); } } - wl_display_disconnect(menu.display); + wl_display_disconnect(menu->display); - if (menu.failure) { + if (menu->failure) { return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/menu.c b/menu.c new file mode 100644 index 0000000..b85cc2a --- /dev/null +++ b/menu.c @@ -0,0 +1,631 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "menu.h" + +#include "pango.h" +#include "render.h" + +static bool parse_color(const char *color, uint32_t *result) { + if (color[0] == '#') { + ++color; + } + size_t len = strlen(color); + if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) { + return false; + } + char *ptr; + uint32_t parsed = (uint32_t)strtoul(color, &ptr, 16); + if (*ptr != '\0') { + return false; + } + *result = len == 6 ? ((parsed << 8) | 0xFF) : parsed; + return true; +} + +// Initialize the menu. +void menu_init(struct menu *menu, int argc, char *argv[]) { + menu->strncmp = strncmp; + menu->font = "monospace 10"; + menu->background = 0x222222ff; + menu->foreground = 0xbbbbbbff; + menu->promptbg = 0x005577ff; + menu->promptfg = 0xeeeeeeff; + menu->selectionbg = 0x005577ff; + menu->selectionfg = 0xeeeeeeff; + + const char *usage = + "Usage: wmenu [-biv] [-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, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) { + switch (opt) { + case 'b': + menu->bottom = true; + break; + case 'i': + menu->strncmp = strncasecmp; + break; + case 'v': + puts("wmenu " VERSION); + exit(EXIT_SUCCESS); + case 'f': + menu->font = optarg; + break; + case 'l': + menu->lines = atoi(optarg); + break; + case 'o': + menu->output_name = optarg; + break; + case 'p': + menu->prompt = optarg; + break; + case 'N': + if (!parse_color(optarg, &menu->background)) { + fprintf(stderr, "Invalid background color: %s", optarg); + } + break; + case 'n': + if (!parse_color(optarg, &menu->foreground)) { + fprintf(stderr, "Invalid foreground color: %s", optarg); + } + break; + case 'M': + if (!parse_color(optarg, &menu->promptbg)) { + fprintf(stderr, "Invalid prompt background color: %s", optarg); + } + break; + case 'm': + if (!parse_color(optarg, &menu->promptfg)) { + fprintf(stderr, "Invalid prompt foreground color: %s", optarg); + } + break; + case 'S': + if (!parse_color(optarg, &menu->selectionbg)) { + fprintf(stderr, "Invalid selection background color: %s", optarg); + } + break; + case 's': + if (!parse_color(optarg, &menu->selectionfg)) { + fprintf(stderr, "Invalid selection foreground color: %s", optarg); + } + break; + default: + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + } + + if (optind < argc) { + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + + int height = get_font_height(menu->font); + menu->line_height = height + 3; + menu->height = menu->line_height; + if (menu->lines > 0) { + menu->height += menu->height * menu->lines; + } + menu->padding = height / 2; +} + +static void append_page(struct page *page, struct page **first, struct page **last) { + if (*last) { + (*last)->next = page; + } else { + *first = page; + } + page->prev = *last; + page->next = NULL; + *last = page; +} + +static void page_items(struct menu *menu) { + // Free existing pages + while (menu->pages != NULL) { + struct page *page = menu->pages; + menu->pages = menu->pages->next; + free(page); + } + + if (!menu->matches) { + return; + } + + // Make new pages + if (menu->lines > 0) { + struct page *pages_end = NULL; + struct item *item = menu->matches; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + for (int i = 1; item && i <= menu->lines; i++) { + item->page = page; + page->last = item; + item = item->next_match; + } + append_page(page, &menu->pages, &pages_end); + } + } else { + // Calculate available space + int max_width = menu->width - menu->inputw - menu->promptw + - menu->left_arrow - menu->right_arrow; + + struct page *pages_end = NULL; + struct item *item = menu->matches; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + int total_width = 0; + while (item) { + total_width += item->width + 2 * menu->padding; + if (total_width > max_width) { + break; + } + + item->page = page; + page->last = item; + item = item->next_match; + } + append_page(page, &menu->pages, &pages_end); + } + } +} + +static const char *fstrstr(struct menu *menu, const char *s, const char *sub) { + for (size_t len = strlen(sub); *s; s++) { + if (!menu->strncmp(s, sub, len)) { + return s; + } + } + return NULL; +} + +static void append_item(struct item *item, struct item **first, struct item **last) { + if (*last) { + (*last)->next_match = item; + } else { + *first = item; + } + item->prev_match = *last; + item->next_match = NULL; + *last = item; +} + +static void match_items(struct menu *menu) { + struct item *lexact = NULL, *exactend = NULL; + struct item *lprefix = NULL, *prefixend = NULL; + struct item *lsubstr = NULL, *substrend = NULL; + char buf[sizeof menu->input], *tok; + char **tokv = NULL; + int i, tokc = 0; + size_t tok_len; + menu->matches = NULL; + menu->matches_end = NULL; + menu->sel = NULL; + + size_t text_len = strlen(menu->input); + + /* tokenize text by space for matching the tokens individually */ + strcpy(buf, menu->input); + tok = strtok(buf, " "); + while (tok) { + tokv = realloc(tokv, (tokc + 1) * sizeof *tokv); + if (!tokv) { + fprintf(stderr, "could not realloc %zu bytes", + (tokc + 1) * sizeof *tokv); + exit(EXIT_FAILURE); + } + tokv[tokc] = tok; + tokc++; + tok = strtok(NULL, " "); + } + tok_len = tokc ? strlen(tokv[0]) : 0; + + 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 */ + break; + } + } + if (i != tokc) { + /* not all tokens match */ + continue; + } + if (!tokc || !menu->strncmp(menu->input, item->text, text_len + 1)) { + append_item(item, &lexact, &exactend); + } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { + append_item(item, &lprefix, &prefixend); + } else { + append_item(item, &lsubstr, &substrend); + } + } + + if (lexact) { + menu->matches = lexact; + menu->matches_end = exactend; + } + if (lprefix) { + if (menu->matches_end) { + menu->matches_end->next_match = lprefix; + lprefix->prev_match = menu->matches_end; + } else { + menu->matches = lprefix; + } + menu->matches_end = prefixend; + } + if (lsubstr) { + if (menu->matches_end) { + menu->matches_end->next_match = lsubstr; + lsubstr->prev_match = menu->matches_end; + } else { + menu->matches = lsubstr; + } + menu->matches_end = substrend; + } + + page_items(menu); + if (menu->pages) { + menu->sel = menu->pages->first; + } +} + +// Read menu items from standard input. +void read_menu_items(struct menu *menu) { + char buf[sizeof menu->input], *p; + struct item *item, **end; + + for(end = &menu->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { + if((p = strchr(buf, '\n'))) { + *p = '\0'; + } + item = malloc(sizeof *item); + if (!item) { + return; + } + + item->text = strdup(buf); + item->next = item->prev_match = item->next_match = NULL; + } + + calc_widths(menu); + match_items(menu); +} + +static void insert(struct menu *menu, const char *s, ssize_t n) { + if (strlen(menu->input) + n > sizeof menu->input - 1) { + return; + } + memmove(menu->input + menu->cursor + n, menu->input + menu->cursor, + sizeof menu->input - menu->cursor - MAX(n, 0)); + if (n > 0 && s != NULL) { + memcpy(menu->input + menu->cursor, s, n); + } + menu->cursor += n; +} + +static size_t nextrune(struct menu *menu, int incr) { + size_t n, len; + + len = strlen(menu->input); + for(n = menu->cursor + incr; n < len && (menu->input[n] & 0xc0) == 0x80; n += incr); + return n; +} + +// Move the cursor to the beginning or end of the word, skipping over any preceding whitespace. +static void movewordedge(struct menu *menu, int dir) { + size_t len = strlen(menu->input); + while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] == ' ') { + menu->cursor = nextrune(menu, dir); + } + while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] != ' ') { + menu->cursor = nextrune(menu, dir); + } +} + +// Handle a keypress. +void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, + xkb_keysym_t sym) { + if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { + return; + } + + bool ctrl = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + XKB_MOD_NAME_CTRL, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + bool meta = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + XKB_MOD_NAME_ALT, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + bool shift = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + XKB_MOD_NAME_SHIFT, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + + size_t len = strlen(menu->input); + + if (ctrl) { + // Emacs-style line editing bindings + switch (sym) { + case XKB_KEY_a: + sym = XKB_KEY_Home; + break; + case XKB_KEY_b: + sym = XKB_KEY_Left; + break; + case XKB_KEY_c: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_d: + sym = XKB_KEY_Delete; + break; + case XKB_KEY_e: + sym = XKB_KEY_End; + break; + case XKB_KEY_f: + sym = XKB_KEY_Right; + break; + case XKB_KEY_g: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_bracketleft: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_h: + sym = XKB_KEY_BackSpace; + break; + case XKB_KEY_i: + sym = XKB_KEY_Tab; + break; + case XKB_KEY_j: + case XKB_KEY_J: + case XKB_KEY_m: + case XKB_KEY_M: + sym = XKB_KEY_Return; + ctrl = false; + break; + case XKB_KEY_n: + sym = XKB_KEY_Down; + break; + case XKB_KEY_p: + sym = XKB_KEY_Up; + break; + + case XKB_KEY_k: + // Delete right + menu->input[menu->cursor] = '\0'; + match_items(menu); + render_menu(menu); + return; + case XKB_KEY_u: + // Delete left + insert(menu, NULL, 0 - menu->cursor); + match_items(menu); + render_menu(menu); + return; + case XKB_KEY_w: + // Delete word + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + } + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + } + match_items(menu); + render_menu(menu); + return; + case XKB_KEY_Y: + // Paste clipboard + if (!menu->offer) { + return; + } + + int fds[2]; + if (pipe(fds) == -1) { + // Pipe failed + return; + } + wl_data_offer_receive(menu->offer, "text/plain", fds[1]); + close(fds[1]); + + wl_display_roundtrip(menu->display); + + while (true) { + char buf[1024]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) { + break; + } + insert(menu, buf, n); + } + close(fds[0]); + + wl_data_offer_destroy(menu->offer); + menu->offer = NULL; + match_items(menu); + render_menu(menu); + return; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + movewordedge(menu, -1); + render_menu(menu); + return; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + movewordedge(menu, +1); + render_menu(menu); + return; + + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + break; + default: + return; + } + } else if (meta) { + // Emacs-style line editing bindings + switch (sym) { + case XKB_KEY_b: + movewordedge(menu, -1); + render_menu(menu); + return; + case XKB_KEY_f: + movewordedge(menu, +1); + render_menu(menu); + return; + case XKB_KEY_g: + sym = XKB_KEY_Home; + break; + case XKB_KEY_G: + sym = XKB_KEY_End; + break; + case XKB_KEY_h: + sym = XKB_KEY_Up; + break; + case XKB_KEY_j: + sym = XKB_KEY_Next; + break; + case XKB_KEY_k: + sym = XKB_KEY_Prior; + break; + case XKB_KEY_l: + sym = XKB_KEY_Down; + break; + default: + return; + } + } + + char buf[8]; + switch (sym) { + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + if (shift) { + puts(menu->input); + fflush(stdout); + menu->exit = true; + } else { + char *text = menu->sel ? menu->sel->text : menu->input; + puts(text); + fflush(stdout); + if (!ctrl) { + menu->exit = true; + } + } + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + case XKB_KEY_Up: + case XKB_KEY_KP_Up: + if (menu->sel && menu->sel->prev_match) { + menu->sel = menu->sel->prev_match; + render_menu(menu); + } else if (menu->cursor > 0) { + menu->cursor = nextrune(menu, -1); + render_menu(menu); + } + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + case XKB_KEY_Down: + case XKB_KEY_KP_Down: + if (menu->cursor < len) { + menu->cursor = nextrune(menu, +1); + render_menu(menu); + } else if (menu->sel && menu->sel->next_match) { + menu->sel = menu->sel->next_match; + 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; + 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; + render_menu(menu); + } + break; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + if (menu->sel == menu->matches) { + menu->cursor = 0; + render_menu(menu); + } else { + menu->sel = menu->matches; + render_menu(menu); + } + break; + case XKB_KEY_End: + case XKB_KEY_KP_End: + if (menu->cursor < len) { + menu->cursor = len; + render_menu(menu); + } else { + menu->sel = menu->matches_end; + render_menu(menu); + } + break; + case XKB_KEY_BackSpace: + if (menu->cursor > 0) { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + match_items(menu); + render_menu(menu); + } + break; + case XKB_KEY_Delete: + case XKB_KEY_KP_Delete: + if (menu->cursor == len) { + return; + } + menu->cursor = nextrune(menu, +1); + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + match_items(menu); + render_menu(menu); + break; + case XKB_KEY_Tab: + if (!menu->sel) { + return; + } + menu->cursor = strnlen(menu->sel->text, sizeof menu->input - 1); + memcpy(menu->input, menu->sel->text, menu->cursor); + menu->input[menu->cursor] = '\0'; + match_items(menu); + render_menu(menu); + break; + case XKB_KEY_Escape: + menu->exit = true; + menu->failure = true; + break; + default: + if (xkb_keysym_to_utf8(sym, buf, 8)) { + insert(menu, buf, strnlen(buf, 8)); + match_items(menu); + render_menu(menu); + } + } +} diff --git a/menu.h b/menu.h new file mode 100644 index 0000000..334b628 --- /dev/null +++ b/menu.h @@ -0,0 +1,102 @@ +#ifndef WMENU_MENU_H +#define WMENU_MENU_H + +#include + +#include "pool-buffer.h" + +// 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 +}; + +// A page of menu items. +struct page { + struct item *first; // first item in the page + struct item *last; // last item in the page + struct page *prev; // previous page + struct page *next; // next page +}; + +// A Wayland output. +struct output { + struct menu *menu; + struct wl_output *output; + int32_t scale; +}; + +// Keyboard state. +struct keyboard { + struct menu *menu; + + struct xkb_context *xkb_context; + struct xkb_state *xkb_state; + + int repeat_timer; + int repeat_delay; + int repeat_period; + enum wl_keyboard_key_state repeat_key_state; + xkb_keysym_t repeat_sym; +}; + +// Menu state. +struct menu { + struct wl_compositor *compositor; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_data_device_manager *data_device_manager; + struct zwlr_layer_shell_v1 *layer_shell; + + struct wl_display *display; + struct wl_surface *surface; + struct wl_data_offer *offer; + + struct keyboard *keyboard; + struct output *output; + char *output_name; + + struct pool_buffer buffers[2]; + struct pool_buffer *current; + + int width; + int height; + int line_height; + int padding; + int inputw; + int promptw; + int left_arrow; + int right_arrow; + + bool bottom; + int (*strncmp)(const char *, const char *, size_t); + char *font; + int lines; + char *prompt; + uint32_t background, foreground; + uint32_t promptbg, promptfg; + uint32_t selectionbg, selectionfg; + + char input[BUFSIZ]; + size_t cursor; + + struct item *items; // list of all items + 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 + + bool exit; + bool failure; +}; + +void menu_init(struct menu *menu, int argc, char *argv[]); +void read_menu_items(struct menu *menu); +void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, + xkb_keysym_t sym); + +#endif diff --git a/meson.build b/meson.build index 2350694..98100ec 100644 --- a/meson.build +++ b/meson.build @@ -37,8 +37,10 @@ executable( 'wmenu', files( 'main.c', + 'menu.c', 'pango.c', 'pool-buffer.c', + 'render.c', ), dependencies: [ cairo, diff --git a/pango.c b/pango.c index 390e263..c43c7b9 100644 --- a/pango.c +++ b/pango.c @@ -5,7 +5,9 @@ #include #include -int get_font_height(char *fontstr) { +#include "pango.h" + +int get_font_height(const char *fontstr) { PangoFontMap *fontmap = pango_cairo_font_map_get_default(); PangoContext *context = pango_font_map_create_context(fontmap); PangoFontDescription *desc = pango_font_description_from_string(fontstr); diff --git a/pango.h b/pango.h index 556b02b..482d49a 100644 --- a/pango.h +++ b/pango.h @@ -1,5 +1,5 @@ -#ifndef DMENU_PANGO_H -#define DMENU_PANGO_H +#ifndef WMENU_PANGO_H +#define WMENU_PANGO_H #include #include #include diff --git a/pool-buffer.c b/pool-buffer.c index a4cf513..daa84fd 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -11,6 +11,7 @@ #include #include #include + #include "pool-buffer.h" static void randname(char *buf) { diff --git a/pool-buffer.h b/pool-buffer.h index 1549041..e07802d 100644 --- a/pool-buffer.h +++ b/pool-buffer.h @@ -1,4 +1,7 @@ /* Taken from sway. MIT licensed */ +#ifndef WMENU_POOL_BUFFER_H +#define WMENU_POOL_BUFFER_H + #include #include #include @@ -19,3 +22,5 @@ struct pool_buffer { struct pool_buffer *get_next_buffer(struct wl_shm *shm, struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale); void destroy_buffer(struct pool_buffer *buffer); + +#endif diff --git a/render.c b/render.c new file mode 100644 index 0000000..6ff582f --- /dev/null +++ b/render.c @@ -0,0 +1,199 @@ +#include + +#include "render.h" + +#include "menu.h" +#include "pango.h" + +// Calculate text widths. +void calc_widths(struct menu *menu) { + cairo_t *cairo = menu->current->cairo; + + // Calculate prompt width + if (menu->prompt) { + menu->promptw = text_width(cairo, menu->font, menu->prompt) + menu->padding + menu->padding/2; + } else { + menu->promptw = 0; + } + + // Calculate scroll indicator widths + menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * menu->padding; + 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) { + item->width = text_width(cairo, menu->font, item->text); + if (item->width > menu->inputw) { + menu->inputw = item->width; + } + } +} + +static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { + cairo_set_source_rgba(cairo, + (color >> (3*8) & 0xFF) / 255.0, + (color >> (2*8) & 0xFF) / 255.0, + (color >> (1*8) & 0xFF) / 255.0, + (color >> (0*8) & 0xFF) / 255.0); +} + +// Renders text to cairo. +static int render_text(struct menu *menu, cairo_t *cairo, const char *str, + int x, int y, int width, uint32_t bg_color, uint32_t fg_color, + int left_padding, int right_padding) { + + int text_width, text_height; + get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); + int text_y = (menu->line_height / 2.0) - (text_height / 2.0); + + if (width == 0) { + width = text_width + left_padding + right_padding; + } + if (bg_color) { + cairo_set_source_u32(cairo, bg_color); + cairo_rectangle(cairo, x, y, width, menu->line_height); + cairo_fill(cairo); + } + cairo_move_to(cairo, x + left_padding, y + text_y); + cairo_set_source_u32(cairo, fg_color); + pango_printf(cairo, menu->font, 1, str); + + return width; +} + +// Renders the prompt message. +static void render_prompt(struct menu *menu, cairo_t *cairo) { + if (!menu->prompt) { + return; + } + render_text(menu, cairo, menu->prompt, 0, 0, 0, + menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); +} + +// 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->foreground, menu->padding, menu->padding); +} + +// Renders a cursor for the input field. +static void render_cursor(struct menu *menu, cairo_t *cairo) { + const int cursor_width = 2; + const int cursor_margin = 2; + int cursor_pos = menu->promptw + menu->padding + + text_width(cairo, menu->font, menu->input) + - text_width(cairo, menu->font, &menu->input[menu->cursor]) + - cursor_width / 2; + cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, + menu->line_height - 2 * cursor_margin); + cairo_fill(cairo); +} + +// Renders a single menu item horizontally. +static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; + + return render_text(menu, cairo, item->text, x, 0, 0, + bg_color, fg_color, menu->padding, menu->padding); +} + +// Renders a single menu item vertically. +static int render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; + + render_text(menu, cairo, item->text, x, y, menu->width - x, + bg_color, fg_color, menu->padding, 0); + return menu->line_height; +} + +// Renders a page of menu items horizontally. +static void render_horizontal_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw + menu->inputw + menu->left_arrow; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + x += render_horizontal_item(menu, cairo, item, x); + } + + // Draw left and right scroll indicators if necessary + if (page->prev) { + cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); + pango_printf(cairo, menu->font, 1, "<"); + } + if (page->next) { + cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); + pango_printf(cairo, menu->font, 1, ">"); + } +} + +// Renders a page of menu items vertically. +static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw; + int y = menu->line_height; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + y += render_vertical_item(menu, cairo, item, x, y); + } +} + +// Renders the menu to cairo. +static void render_to_cairo(struct menu *menu, cairo_t *cairo) { + // Render background + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, menu->background); + cairo_paint(cairo); + + // Render prompt and input + render_prompt(menu, cairo); + render_input(menu, cairo); + render_cursor(menu, cairo); + + // Render selected page + if (!menu->sel) { + return; + } + if (menu->lines > 0) { + render_vertical_page(menu, cairo, menu->sel->page); + } else { + render_horizontal_page(menu, cairo, menu->sel->page); + } +} + +// Renders a single frame of the menu. +void render_menu(struct menu *menu) { + 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 = menu->output ? menu->output->scale : 1; + menu->current = get_next_buffer(menu->shm, + menu->buffers, menu->width, menu->height, scale); + if (!menu->current) { + goto cleanup; + } + + cairo_t *shm = menu->current->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); + + wl_surface_set_buffer_scale(menu->surface, scale); + wl_surface_attach(menu->surface, menu->current->buffer, 0, 0); + wl_surface_damage(menu->surface, 0, 0, menu->width, menu->height); + wl_surface_commit(menu->surface); + +cleanup: + cairo_destroy(cairo); +} diff --git a/render.h b/render.h new file mode 100644 index 0000000..af5fb67 --- /dev/null +++ b/render.h @@ -0,0 +1,9 @@ +#ifndef WMENU_RENDER_H +#define WMENU_RENDER_H + +#include "menu.h" + +void calc_widths(struct menu *menu); +void render_menu(struct menu *menu); + +#endif From 0db7efe2324a4b2656fa117d9c7bdf84fc7fcbc1 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 11:34:17 -0500 Subject: [PATCH 33/72] Simplify read_menu_items --- menu.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/menu.c b/menu.c index b85cc2a..c646666 100644 --- a/menu.c +++ b/menu.c @@ -293,20 +293,22 @@ static void match_items(struct menu *menu) { // Read menu items from standard input. void read_menu_items(struct menu *menu) { - char buf[sizeof menu->input], *p; - struct item *item, **end; + char buf[sizeof menu->input]; - for(end = &menu->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { - if((p = strchr(buf, '\n'))) { + struct item **next = &menu->items; + while (fgets(buf, sizeof buf, stdin)) { + char *p = strchr(buf, '\n'); + if (p) { *p = '\0'; } - item = malloc(sizeof *item); + struct item *item = calloc(1, sizeof *item); if (!item) { return; } - item->text = strdup(buf); - item->next = item->prev_match = item->next_match = NULL; + + *next = item; + next = &item->next; } calc_widths(menu); From 9f6a36d73fb185db5b903c7a4e4cabed2990accd Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 11:40:34 -0500 Subject: [PATCH 34/72] Drop unnecessary TODO comment --- main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/main.c b/main.c index e74a07c..fb1c3d0 100644 --- a/main.c +++ b/main.c @@ -83,7 +83,6 @@ static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); struct keyboard *keyboard = data; - // TODO: MAP_PRIVATE vs MAP_SHARED char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); assert (map_shm != MAP_FAILED); From bbfbf8f36c1e7dd1912d0646a3ae271a8c69e6b9 Mon Sep 17 00:00:00 2001 From: adnano Date: Tue, 27 Feb 2024 12:00:10 -0500 Subject: [PATCH 35/72] Revert "Simplify movewordedge" This reverts commit 8bcad262a4d047140767d9467ac5526bb768a95e. --- menu.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/menu.c b/menu.c index c646666..7f4f461 100644 --- a/menu.c +++ b/menu.c @@ -337,12 +337,23 @@ static size_t nextrune(struct menu *menu, int incr) { // Move the cursor to the beginning or end of the word, skipping over any preceding whitespace. static void movewordedge(struct menu *menu, int dir) { - size_t len = strlen(menu->input); - while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] == ' ') { - menu->cursor = nextrune(menu, dir); - } - while (menu->cursor > 0 && menu->cursor < len && menu->input[nextrune(menu, dir)] != ' ') { - menu->cursor = nextrune(menu, dir); + if (dir < 0) { + // Move to beginning of word + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { + menu->cursor = nextrune(menu, -1); + } + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { + menu->cursor = nextrune(menu, -1); + } + } else { + // Move to end of word + size_t len = strlen(menu->input); + while (menu->cursor < len && menu->input[menu->cursor] == ' ') { + menu->cursor = nextrune(menu, +1); + } + while (menu->cursor < len && menu->input[menu->cursor] != ' ') { + menu->cursor = nextrune(menu, +1); + } } } From ff4d1f8f8ee392d52b71858fce658f70ee26f434 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 1 Mar 2024 20:54:12 -0500 Subject: [PATCH 36/72] Fix output selection with -o flag --- main.c | 10 ++++++---- menu.h | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index fb1c3d0..3c26647 100644 --- a/main.c +++ b/main.c @@ -27,7 +27,8 @@ static void noop() { static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { struct menu *menu = data; - menu->output = wl_output_get_user_data(wl_output); + struct output *output = wl_output_get_user_data(wl_output); + menu->output = output; } static const struct wl_surface_listener surface_listener = { @@ -62,9 +63,10 @@ static void output_scale(void *data, struct wl_output *wl_output, int32_t factor static void output_name(void *data, struct wl_output *wl_output, const char *name) { struct output *output = data; + output->name = name; + struct menu *menu = output->menu; - char *outname = menu->output_name; - if (!menu->output && outname && strcmp(outname, name) == 0) { + if (menu->output_name && strcmp(menu->output_name, name) == 0) { menu->output = output; } } @@ -258,7 +260,7 @@ static void create_surface(struct menu *menu) { struct zwlr_layer_surface_v1 *layer_surface = zwlr_layer_shell_v1_get_layer_surface( menu->layer_shell, menu->surface, - NULL, + menu->output ? menu->output->output : NULL, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "menu" ); diff --git a/menu.h b/menu.h index 334b628..3562fd2 100644 --- a/menu.h +++ b/menu.h @@ -27,6 +27,7 @@ struct page { struct output { struct menu *menu; struct wl_output *output; + const char *name; int32_t scale; }; From b247119ab3ad46760ac85428309230200d9f6c61 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 2 Mar 2024 07:32:43 -0500 Subject: [PATCH 37/72] Rename text_len to input_len --- menu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/menu.c b/menu.c index 7f4f461..92a1154 100644 --- a/menu.c +++ b/menu.c @@ -223,9 +223,9 @@ static void match_items(struct menu *menu) { menu->matches_end = NULL; menu->sel = NULL; - size_t text_len = strlen(menu->input); + size_t input_len = strlen(menu->input); - /* tokenize text by space for matching the tokens individually */ + /* tokenize input by space for matching the tokens individually */ strcpy(buf, menu->input); tok = strtok(buf, " "); while (tok) { @@ -253,7 +253,7 @@ static void match_items(struct menu *menu) { /* not all tokens match */ continue; } - if (!tokc || !menu->strncmp(menu->input, item->text, text_len + 1)) { + if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) { append_item(item, &lexact, &exactend); } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { append_item(item, &lprefix, &prefixend); From f7e6e0b4bf3bca6dbc2b6c7e81cb013be43c6c06 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 2 Mar 2024 11:31:13 -0500 Subject: [PATCH 38/72] Free memory associated with the menu on exit --- main.c | 89 ++++++++++++++------------------- menu.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++-------- menu.h | 60 ++++++++++++++-------- pango.c | 8 ++- render.c | 13 ++--- 5 files changed, 220 insertions(+), 99 deletions(-) diff --git a/main.c b/main.c index 3c26647..7b43b43 100644 --- a/main.c +++ b/main.c @@ -24,11 +24,9 @@ static void noop() { // Do nothing } -static void surface_enter(void *data, struct wl_surface *surface, - struct wl_output *wl_output) { +static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { struct menu *menu = data; - struct output *output = wl_output_get_user_data(wl_output); - menu->output = output; + menu->output = wl_output_get_user_data(wl_output); } static const struct wl_surface_listener surface_listener = { @@ -45,8 +43,7 @@ static void layer_surface_configure(void *data, zwlr_layer_surface_v1_ack_configure(surface, serial); } -static void layer_surface_closed(void *data, - struct zwlr_layer_surface_v1 *surface) { +static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { struct menu *menu = data; menu->exit = true; } @@ -82,18 +79,18 @@ static const struct wl_output_listener output_listener = { static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); struct keyboard *keyboard = data; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - assert (map_shm != MAP_FAILED); + assert(map_shm != MAP_FAILED); - struct xkb_keymap *keymap = xkb_keymap_new_from_string(keyboard->xkb_context, + keyboard->keymap = xkb_keymap_new_from_string(keyboard->context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_shm, size); close(fd); - keyboard->xkb_state = xkb_state_new(keymap); + keyboard->state = xkb_state_new(keyboard->keymap); } static void keyboard_repeat(struct keyboard *keyboard) { @@ -109,7 +106,7 @@ static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, struct keyboard *keyboard = data; enum wl_keyboard_key_state key_state = _key_state; - xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->xkb_state, key + 8); + xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->state, key + 8); menu_keypress(keyboard->menu, key_state, sym); if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && keyboard->repeat_period >= 0) { @@ -142,7 +139,7 @@ static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct keyboard *keyboard = data; - xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, + xkb_state_update_mask(keyboard->state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } @@ -159,8 +156,10 @@ static void seat_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct menu *menu = data; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(keyboard, &keyboard_listener, menu->keyboard); + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(seat); + struct keyboard *keyboard = keyboard_create(menu, wl_keyboard); + wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, keyboard); + menu_set_keyboard(menu, keyboard); } } @@ -170,9 +169,9 @@ static const struct wl_seat_listener seat_listener = { }; static void data_device_selection(void *data, struct wl_data_device *data_device, - struct wl_data_offer *offer) { + struct wl_data_offer *data_offer) { struct menu *menu = data; - menu->offer = offer; + menu->data_offer = data_offer; } static const struct wl_data_device_listener data_device_listener = { @@ -188,27 +187,22 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct menu *menu = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { - menu->compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 4); + menu->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } else if (strcmp(interface, wl_shm_interface.name) == 0) { menu->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, wl_seat_interface.name) == 0) { menu->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); wl_seat_add_listener(menu->seat, &seat_listener, menu); } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { - menu->data_device_manager = wl_registry_bind(registry, name, - &wl_data_device_manager_interface, 3); + menu->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - menu->layer_shell = wl_registry_bind(registry, name, - &zwlr_layer_shell_v1_interface, 1); + menu->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (strcmp(interface, wl_output_interface.name) == 0) { - struct output *output = calloc(1, sizeof(struct output)); - output->output = wl_registry_bind(registry, name, - &wl_output_interface, 4); - output->menu = menu; - output->scale = 1; - wl_output_set_user_data(output->output, output); - wl_output_add_listener(output->output, &output_listener, output); + struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, 4); + struct output *output = output_create(menu, wl_output); + wl_output_set_user_data(wl_output, output); + wl_output_add_listener(wl_output, &output_listener, output); + menu_add_output(menu, output); } } @@ -217,15 +211,8 @@ static const struct wl_registry_listener registry_listener = { .global_remove = noop, }; -static void keyboard_init(struct keyboard *keyboard, struct menu *menu) { - keyboard->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - assert(keyboard->xkb_context != NULL); - keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); - assert(keyboard->repeat_timer >= 0); - keyboard->menu = menu; -} - -static void create_surface(struct menu *menu) { +// Connect to the Wayland display. +static void menu_connect(struct menu *menu) { menu->display = wl_display_connect(NULL); if (!menu->display) { fprintf(stderr, "Failed to connect to display.\n"); @@ -240,14 +227,17 @@ static void create_surface(struct menu *menu) { assert(menu->seat != NULL); assert(menu->data_device_manager != NULL); assert(menu->layer_shell != NULL); + menu->registry = registry; // Get data device for seat struct wl_data_device *data_device = wl_data_device_manager_get_data_device( menu->data_device_manager, menu->seat); wl_data_device_add_listener(data_device, &data_device_listener, menu); + menu->data_device = data_device; - // Second roundtrip for xdg-output + // Second roundtrip for seat and output listeners wl_display_roundtrip(menu->display); + assert(menu->keyboard != NULL); if (menu->output_name && !menu->output) { fprintf(stderr, "Output %s not found\n", menu->output_name); @@ -265,6 +255,7 @@ static void create_surface(struct menu *menu) { "menu" ); assert(layer_surface != NULL); + menu->layer_surface = layer_surface; uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; @@ -285,14 +276,9 @@ static void create_surface(struct menu *menu) { } int main(int argc, char *argv[]) { - struct menu *menu = calloc(1, sizeof(struct menu)); - menu_init(menu, argc, argv); - - struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); - keyboard_init(keyboard, menu); - menu->keyboard = keyboard; - - create_surface(menu); + struct menu *menu = menu_create(); + menu_getopts(menu, argc, argv); + menu_connect(menu); render_menu(menu); read_menu_items(menu); @@ -300,7 +286,7 @@ int main(int argc, char *argv[]) { struct pollfd fds[] = { { wl_display_get_fd(menu->display), POLLIN }, - { keyboard->repeat_timer, POLLIN }, + { menu->keyboard->repeat_timer, POLLIN }, }; const size_t nfds = sizeof(fds) / sizeof(*fds); @@ -325,13 +311,14 @@ int main(int argc, char *argv[]) { } if (fds[1].revents & POLLIN) { - keyboard_repeat(keyboard); + keyboard_repeat(menu->keyboard); } } - wl_display_disconnect(menu->display); + bool failure = menu->failure; + menu_destroy(menu); - if (menu->failure) { + if (failure) { return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/menu.c b/menu.c index 92a1154..0acfcab 100644 --- a/menu.c +++ b/menu.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -18,7 +19,55 @@ #include "menu.h" #include "pango.h" +#include "pool-buffer.h" #include "render.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +// Creates and returns a new menu. +struct menu *menu_create() { + struct menu *menu = calloc(1, sizeof(struct menu)); + menu->strncmp = strncmp; + menu->font = "monospace 10"; + menu->normalbg = 0x222222ff; + menu->normalfg = 0xbbbbbbff; + menu->promptbg = 0x005577ff; + menu->promptfg = 0xeeeeeeff; + menu->selectionbg = 0x005577ff; + menu->selectionfg = 0xeeeeeeff; + return menu; +} + +// Creates and returns a new keyboard. +struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard) { + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard->menu = menu; + keyboard->keyboard = wl_keyboard; + keyboard->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + assert(keyboard->context != NULL); + keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); + assert(keyboard->repeat_timer != -1); + return keyboard; +} + +// Sets the current keyboard. +void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard) { + menu->keyboard = keyboard; +} + +// Creates and returns a new output. +struct output *output_create(struct menu *menu, struct wl_output *wl_output) { + struct output *output = calloc(1, sizeof(struct output)); + output->menu = menu; + output->output = wl_output; + output->scale = 1; + return output; +} + +// Adds an output to the output list. +void menu_add_output(struct menu *menu, struct output *output) { + output->next = menu->output_list; + menu->output_list = output; +} static bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { @@ -37,17 +86,8 @@ static bool parse_color(const char *color, uint32_t *result) { return true; } -// Initialize the menu. -void menu_init(struct menu *menu, int argc, char *argv[]) { - menu->strncmp = strncmp; - menu->font = "monospace 10"; - menu->background = 0x222222ff; - menu->foreground = 0xbbbbbbff; - menu->promptbg = 0x005577ff; - menu->promptfg = 0xeeeeeeff; - menu->selectionbg = 0x005577ff; - menu->selectionfg = 0xeeeeeeff; - +// Parse menu options from command line arguments. +void menu_getopts(struct menu *menu, int argc, char *argv[]) { const char *usage = "Usage: wmenu [-biv] [-f font] [-l lines] [-o output] [-p prompt]\n" "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n"; @@ -77,12 +117,12 @@ void menu_init(struct menu *menu, int argc, char *argv[]) { menu->prompt = optarg; break; case 'N': - if (!parse_color(optarg, &menu->background)) { + if (!parse_color(optarg, &menu->normalbg)) { fprintf(stderr, "Invalid background color: %s", optarg); } break; case 'n': - if (!parse_color(optarg, &menu->foreground)) { + if (!parse_color(optarg, &menu->normalfg)) { fprintf(stderr, "Invalid foreground color: %s", optarg); } break; @@ -262,6 +302,8 @@ static void match_items(struct menu *menu) { } } + free(tokv); + if (lexact) { menu->matches = lexact; menu->matches_end = exactend; @@ -364,13 +406,13 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; } - bool ctrl = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + bool ctrl = xkb_state_mod_name_is_active(menu->keyboard->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool meta = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + bool meta = xkb_state_mod_name_is_active(menu->keyboard->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool shift = xkb_state_mod_name_is_active(menu->keyboard->xkb_state, + bool shift = xkb_state_mod_name_is_active(menu->keyboard->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); @@ -448,7 +490,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; case XKB_KEY_Y: // Paste clipboard - if (!menu->offer) { + if (!menu->data_offer) { return; } @@ -457,7 +499,7 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, // Pipe failed return; } - wl_data_offer_receive(menu->offer, "text/plain", fds[1]); + wl_data_offer_receive(menu->data_offer, "text/plain", fds[1]); close(fds[1]); wl_display_roundtrip(menu->display); @@ -472,8 +514,8 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, } close(fds[0]); - wl_data_offer_destroy(menu->offer); - menu->offer = NULL; + wl_data_offer_destroy(menu->data_offer); + menu->data_offer = NULL; match_items(menu); render_menu(menu); return; @@ -642,3 +684,70 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, } } } + +// Frees the keyboard. +static void free_keyboard(struct keyboard *keyboard) { + wl_keyboard_release(keyboard->keyboard); + xkb_state_unref(keyboard->state); + xkb_keymap_unref(keyboard->keymap); + xkb_context_unref(keyboard->context); + free(keyboard); +} + +// Frees the outputs. +static void free_outputs(struct menu *menu) { + struct output *next = menu->output_list; + while (next) { + struct output *output = next; + next = output->next; + wl_output_destroy(output->output); + free(output); + } +} + +// Frees menu pages. +static void free_pages(struct menu *menu) { + struct page *next = menu->pages; + while (next) { + struct page *page = next; + next = page->next; + free(page); + } +} + +// Frees menu items. +static void free_items(struct menu *menu) { + struct item *next = menu->items; + while (next) { + struct item *item = next; + next = item->next; + free(item->text); + free(item); + } +} + +// Destroys the menu, freeing memory associated with it. +void menu_destroy(struct menu *menu) { + wl_registry_destroy(menu->registry); + wl_compositor_destroy(menu->compositor); + wl_shm_destroy(menu->shm); + wl_seat_destroy(menu->seat); + wl_data_device_manager_destroy(menu->data_device_manager); + zwlr_layer_shell_v1_destroy(menu->layer_shell); + free_outputs(menu); + + free_keyboard(menu->keyboard); + wl_data_device_destroy(menu->data_device); + wl_surface_destroy(menu->surface); + zwlr_layer_surface_v1_destroy(menu->layer_surface); + wl_data_offer_destroy(menu->data_offer); + + free_pages(menu); + free_items(menu); + + destroy_buffer(&menu->buffers[0]); + destroy_buffer(&menu->buffers[1]); + + wl_display_disconnect(menu->display); + free(menu); +} diff --git a/menu.h b/menu.h index 3562fd2..4c62d3f 100644 --- a/menu.h +++ b/menu.h @@ -27,16 +27,18 @@ struct page { struct output { struct menu *menu; struct wl_output *output; - const char *name; - int32_t scale; + const char *name; // output name + int32_t scale; // output scale + struct output *next; // next output }; // Keyboard state. struct keyboard { struct menu *menu; - - struct xkb_context *xkb_context; - struct xkb_state *xkb_state; + struct wl_keyboard *keyboard; + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; int repeat_timer; int repeat_delay; @@ -47,19 +49,40 @@ struct keyboard { // Menu state. struct menu { + // Whether the menu appears at the bottom of the screen + bool bottom; + // The function used to match menu items + int (*strncmp)(const char *, const char *, size_t); + // The font used to display the menu + char *font; + // The number of lines to list items vertically + int lines; + // The name of the output to display on + char *output_name; + // The prompt displayed to the left of the input field + char *prompt; + // Normal colors + uint32_t normalbg, normalfg; + // Prompt colors + uint32_t promptbg, promptfg; + // Selection colors + uint32_t selectionbg, selectionfg; + + struct wl_display *display; + struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; struct wl_seat *seat; struct wl_data_device_manager *data_device_manager; struct zwlr_layer_shell_v1 *layer_shell; - - struct wl_display *display; - struct wl_surface *surface; - struct wl_data_offer *offer; + struct output *output_list; struct keyboard *keyboard; + struct wl_data_device *data_device; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_data_offer *data_offer; struct output *output; - char *output_name; struct pool_buffer buffers[2]; struct pool_buffer *current; @@ -73,15 +96,6 @@ struct menu { int left_arrow; int right_arrow; - bool bottom; - int (*strncmp)(const char *, const char *, size_t); - char *font; - int lines; - char *prompt; - uint32_t background, foreground; - uint32_t promptbg, promptfg; - uint32_t selectionbg, selectionfg; - char input[BUFSIZ]; size_t cursor; @@ -95,9 +109,15 @@ struct menu { bool failure; }; -void menu_init(struct menu *menu, int argc, char *argv[]); +struct menu *menu_create(); +struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard); +void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard); +struct output *output_create(struct menu *menu, struct wl_output *wl_output); +void menu_add_output(struct menu *menu, struct output *output); +void menu_getopts(struct menu *menu, int argc, char *argv[]); void read_menu_items(struct menu *menu); void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, xkb_keysym_t sym); +void menu_destroy(struct menu *menu); #endif diff --git a/pango.c b/pango.c index c43c7b9..07d4c5b 100644 --- a/pango.c +++ b/pango.c @@ -13,12 +13,16 @@ int get_font_height(const char *fontstr) { PangoFontDescription *desc = pango_font_description_from_string(fontstr); PangoFont *font = pango_font_map_load_font(fontmap, context, desc); if (font == NULL) { + pango_font_description_free(desc); + g_object_unref(context); return -1; } PangoFontMetrics *metrics = pango_font_get_metrics(font, NULL); int height = pango_font_metrics_get_height(metrics) / PANGO_SCALE; - pango_font_description_free(desc); pango_font_metrics_unref(metrics); + g_object_unref(font); + pango_font_description_free(desc); + g_object_unref(context); return height; } @@ -32,8 +36,8 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, pango_layout_set_font_description(layout, desc); pango_layout_set_single_paragraph_mode(layout, 1); pango_layout_set_attributes(layout, attrs); - pango_attr_list_unref(attrs); pango_font_description_free(desc); + pango_attr_list_unref(attrs); return layout; } diff --git a/render.c b/render.c index 6ff582f..135d51f 100644 --- a/render.c +++ b/render.c @@ -73,7 +73,7 @@ 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->foreground, menu->padding, menu->padding); + 0, menu->normalfg, menu->padding, menu->padding); } // Renders a cursor for the input field. @@ -91,8 +91,8 @@ static void render_cursor(struct menu *menu, cairo_t *cairo) { // Renders a single menu item horizontally. static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->normalbg; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->normalfg; return render_text(menu, cairo, item->text, x, 0, 0, bg_color, fg_color, menu->padding, menu->padding); @@ -100,8 +100,8 @@ static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item // Renders a single menu item vertically. static int render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { - uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->background; - uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->foreground; + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->normalbg; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->normalfg; render_text(menu, cairo, item->text, x, y, menu->width - x, bg_color, fg_color, menu->padding, 0); @@ -139,7 +139,7 @@ static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page static void render_to_cairo(struct menu *menu, cairo_t *cairo) { // Render background cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, menu->background); + cairo_set_source_u32(cairo, menu->normalbg); cairo_paint(cairo); // Render prompt and input @@ -196,4 +196,5 @@ void render_menu(struct menu *menu) { cleanup: cairo_destroy(cairo); + cairo_surface_destroy(recorder); } From 4e151795bf8f15e38999a42cb71ddc63423ffe12 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 2 Mar 2024 11:49:47 -0500 Subject: [PATCH 39/72] Version 0.1.7 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 98100ec..b9f4ec9 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.1.6', + version: '0.1.7', license: 'MIT', default_options: [ 'c_std=c11', From 6ad7a303ef2ff130b84cfa718ace423a3101dbbb Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 17 Mar 2024 07:01:23 -0400 Subject: [PATCH 40/72] Don't destroy wl_data_offer twice The data offer is destroyed after it is used. There is no need to destroy it again. This also fixes an issue where calling wl_data_offer_destroy with a NULL data offer would segfault. --- menu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/menu.c b/menu.c index 0acfcab..f48a8f0 100644 --- a/menu.c +++ b/menu.c @@ -740,7 +740,6 @@ void menu_destroy(struct menu *menu) { wl_data_device_destroy(menu->data_device); wl_surface_destroy(menu->surface); zwlr_layer_surface_v1_destroy(menu->layer_surface); - wl_data_offer_destroy(menu->data_offer); free_pages(menu); free_items(menu); From 9e9284666c9fcf3278ad17f98a247658db8b2269 Mon Sep 17 00:00:00 2001 From: sewn Date: Sun, 10 Mar 2024 18:00:48 +0300 Subject: [PATCH 41/72] port dmenu password patch --- docs/wmenu.1.scd | 6 +++++- menu.c | 14 +++++++++++--- menu.h | 2 ++ render.c | 21 +++++++++++++++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 0117aa1..407da9f 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_] \ @@ -30,6 +30,10 @@ to those matching the tokens in the input. *-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 f48a8f0..f95409f 100644 --- a/menu.c +++ b/menu.c @@ -89,11 +89,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 [-biv] [-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; - 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; @@ -101,6 +101,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); @@ -335,8 +338,13 @@ static void match_items(struct menu *menu) { // Read menu items from standard input. void read_menu_items(struct menu *menu) { - char buf[sizeof menu->input]; + if (menu->passwd) { + // Don't read standard input in password mode + calc_widths(menu); + return; + } + char buf[sizeof menu->input]; struct item **next = &menu->items; while (fgets(buf, sizeof buf, stdin)) { char *p = strchr(buf, '\n'); diff --git a/menu.h b/menu.h index 4c62d3f..c9460d6 100644 --- a/menu.h +++ b/menu.h @@ -53,6 +53,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 135d51f..a0a2a8c 100644 --- a/render.c +++ b/render.c @@ -1,4 +1,7 @@ +#define _POSIX_C_SOURCE 200809L #include +#include +#include #include "render.h" @@ -72,8 +75,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. From ac25b0733885b545578092c5da74c9a3122529d6 Mon Sep 17 00:00:00 2001 From: sewn Date: Sun, 10 Mar 2024 18:07:46 +0300 Subject: [PATCH 42/72] add wmenu_run script, similar to dmenu_run script based off the works of sinanmohd, modified to be simpler and better to read, with shellcheck. Co-authored-by: sinanmohd --- meson.build | 2 ++ wmenu_run | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100755 wmenu_run diff --git a/meson.build b/meson.build index b9f4ec9..56fe511 100644 --- a/meson.build +++ b/meson.build @@ -33,6 +33,8 @@ rt = cc.find_library('rt') subdir('protocols') subdir('docs') +install_data('wmenu_run', install_dir: get_option('bindir')) + executable( 'wmenu', files( diff --git a/wmenu_run b/wmenu_run new file mode 100755 index 0000000..b243b38 --- /dev/null +++ b/wmenu_run @@ -0,0 +1,35 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/wmenu_run" + +[ -d "$cachedir" ] || mkdir -p "$cachedir" + +uptodate() { + [ -f "$cache" ] || return 1 + IFS=: + for path in $PATH; do + # non-POSIX + test "$path" -nt "$cache" && return 1 + done + return 0 +} + +bins() { + IFS=: + for path in $PATH; do + for bin in "$path"/*; do + [ -x "$bin" ] && echo "${bin##*/}" + done + done +} + +path() { + if uptodate; then + cat "$cache" + else + bins | sort -u | tee "$cache" + fi +} + +path | wmenu "$@" | ${SHELL:-"/bin/sh"} & From 41b2e8b1e1d1415a43baa6393b7237e7e77468eb Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 25 Mar 2024 08:20:36 -0400 Subject: [PATCH 43/72] menu: Avoid adding zero-size pages Ensure that pages always have at least one item, even if that item is too big to fit on any page. --- menu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/menu.c b/menu.c index f95409f..e4a8e1c 100644 --- a/menu.c +++ b/menu.c @@ -219,11 +219,13 @@ static void page_items(struct menu *menu) { page->first = item; int total_width = 0; + int items = 0; while (item) { total_width += item->width + 2 * menu->padding; - if (total_width > max_width) { + if (total_width > max_width && items > 0) { break; } + items++; item->page = page; page->last = item; From cf6f5b9d06d72ded0b077d6770854d0003c1b4aa Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 7 Apr 2024 08:51:57 -0400 Subject: [PATCH 44/72] Support xdg_activation_v1 protocol --- README.md | 2 +- docs/wmenu.1.scd | 5 +++- main.c | 4 +++ menu.c | 58 +++++++++++++++++++++++++++++++++++++++++-- menu.h | 4 +++ protocols/meson.build | 1 + wmenu_run | 2 +- 7 files changed, 71 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d0e62c4..099f036 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ See wmenu(1) To use wmenu with Sway, you can add the following to your configuration file: ``` -set $menu dmenu_path | wmenu | xargs swaymsg exec -- +set $menu wmenu_run bindsym $mod+d exec $menu ``` diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 407da9f..cfcbd84 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland # SYNOPSIS -*wmenu* [-biPv] \ +*wmenu* [-biPvx] \ [-f _font_] \ [-l _lines_] \ [-o _output_] \ @@ -37,6 +37,9 @@ to those matching the tokens in the input. *-v* prints version information to stdout, then exits. +*-x* + wmenu will execute the selected item. + *-f* _font_ defines the font used. For more information, see https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html diff --git a/main.c b/main.c index 7b43b43..710f75f 100644 --- a/main.c +++ b/main.c @@ -18,6 +18,7 @@ #include "menu.h" #include "render.h" +#include "xdg-activation-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" static void noop() { @@ -203,6 +204,8 @@ static void handle_global(void *data, struct wl_registry *registry, wl_output_set_user_data(wl_output, output); wl_output_add_listener(wl_output, &output_listener, output); menu_add_output(menu, output); + } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + menu->activation = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); } } @@ -227,6 +230,7 @@ static void menu_connect(struct menu *menu) { assert(menu->seat != NULL); assert(menu->data_device_manager != NULL); assert(menu->layer_shell != NULL); + assert(menu->activation != NULL); menu->registry = registry; // Get data device for seat diff --git a/menu.c b/menu.c index e4a8e1c..953aa95 100644 --- a/menu.c +++ b/menu.c @@ -21,6 +21,7 @@ #include "pango.h" #include "pool-buffer.h" #include "render.h" +#include "xdg-activation-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" // Creates and returns a new menu. @@ -89,11 +90,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, "bhiPvxf:l:o:p:N:n:M:m:S:s:")) != -1) { switch (opt) { case 'b': menu->bottom = true; @@ -107,6 +108,9 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { case 'v': puts("wmenu " VERSION); exit(EXIT_SUCCESS); + case 'x': + menu->exec = true; + break; case 'f': menu->font = optarg; break; @@ -409,6 +413,53 @@ static void movewordedge(struct menu *menu, int dir) { } } +// Information needed to execute an item. +struct executable { + struct menu *menu; + char *name; +}; + +// Executes an item with an activation token. +static void execute(struct executable *exe, const char *token) { + menu_destroy(exe->menu); + + setenv("XDG_ACTIVATION_TOKEN", token, true); + execlp(exe->name, exe->name, NULL); + + // Handle execution failure + fprintf(stderr, "Failed to execute selection: %s\n", strerror(errno)); + free(exe->name); + free(exe); + exit(EXIT_FAILURE); +} + +static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, + const char *token) { + struct executable *exe = data; + xdg_activation_token_v1_destroy(activation_token); + execute(exe, token); +} + +static const struct xdg_activation_token_v1_listener activation_token_listener = { + .done = activation_token_done, +}; + +// Executes the selected item. +static void menu_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_token_v1 *activation_token = xdg_activation_v1_get_activation_token(menu->activation); + xdg_activation_token_v1_set_surface(activation_token, menu->surface); + xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, exe); + xdg_activation_token_v1_commit(activation_token); +} + // Handle a keypress. void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, xkb_keysym_t sym) { @@ -588,6 +639,8 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, puts(menu->input); fflush(stdout); menu->exit = true; + } else if (menu->exec) { + menu_exec(menu); } else { char *text = menu->sel ? menu->sel->text : menu->input; puts(text); @@ -750,6 +803,7 @@ void menu_destroy(struct menu *menu) { wl_data_device_destroy(menu->data_device); wl_surface_destroy(menu->surface); zwlr_layer_surface_v1_destroy(menu->layer_surface); + xdg_activation_v1_destroy(menu->activation); free_pages(menu); free_items(menu); diff --git a/menu.h b/menu.h index c9460d6..762dfc8 100644 --- a/menu.h +++ b/menu.h @@ -4,6 +4,7 @@ #include #include "pool-buffer.h" +#include "xdg-activation-v1-client-protocol.h" // A menu item. struct item { @@ -55,6 +56,8 @@ struct menu { int (*strncmp)(const char *, const char *, size_t); // Whether the input is a password bool passwd; + // Whether to execute the selected item + bool exec; // The font used to display the menu char *font; // The number of lines to list items vertically @@ -78,6 +81,7 @@ struct menu { struct wl_data_device_manager *data_device_manager; struct zwlr_layer_shell_v1 *layer_shell; struct output *output_list; + struct xdg_activation_v1 *activation; struct keyboard *keyboard; struct wl_data_device *data_device; diff --git a/protocols/meson.build b/protocols/meson.build index fb7ce6a..5c9973d 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -12,6 +12,7 @@ endif protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ] diff --git a/wmenu_run b/wmenu_run index b243b38..afe96e2 100755 --- a/wmenu_run +++ b/wmenu_run @@ -32,4 +32,4 @@ path() { fi } -path | wmenu "$@" | ${SHELL:-"/bin/sh"} & +path | wmenu -x "$@" From e4c4627eebc57a6af5812c69f6dc3f9992d9c770 Mon Sep 17 00:00:00 2001 From: sewn Date: Tue, 9 Apr 2024 11:12:49 +0300 Subject: [PATCH 45/72] make menu height accurate to dwm, dmenu, and dwl's bar patch --- menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu.c b/menu.c index 953aa95..549b730 100644 --- a/menu.c +++ b/menu.c @@ -165,7 +165,7 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { } int height = get_font_height(menu->font); - menu->line_height = height + 3; + menu->line_height = height + 2; menu->height = menu->line_height; if (menu->lines > 0) { menu->height += menu->height * menu->lines; From 6a39269d2e73273de324ed156eba71e5bdcd9602 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 14:44:09 -0400 Subject: [PATCH 46/72] Drop wmenu -x option --- README.md | 2 +- docs/wmenu.1.scd | 5 +---- menu.c | 54 +----------------------------------------------- menu.h | 2 -- wmenu_run | 2 +- 5 files changed, 4 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 099f036..d0e62c4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ See wmenu(1) To use wmenu with Sway, you can add the following to your configuration file: ``` -set $menu wmenu_run +set $menu dmenu_path | wmenu | xargs swaymsg exec -- bindsym $mod+d exec $menu ``` diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index cfcbd84..407da9f 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland # SYNOPSIS -*wmenu* [-biPvx] \ +*wmenu* [-biPv] \ [-f _font_] \ [-l _lines_] \ [-o _output_] \ @@ -37,9 +37,6 @@ to those matching the tokens in the input. *-v* prints version information to stdout, then exits. -*-x* - wmenu will execute the selected item. - *-f* _font_ defines the font used. For more information, see https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html diff --git a/menu.c b/menu.c index 549b730..72d903f 100644 --- a/menu.c +++ b/menu.c @@ -94,7 +94,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, "bhiPvxf: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; @@ -108,9 +108,6 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { case 'v': puts("wmenu " VERSION); exit(EXIT_SUCCESS); - case 'x': - menu->exec = true; - break; case 'f': menu->font = optarg; break; @@ -413,53 +410,6 @@ static void movewordedge(struct menu *menu, int dir) { } } -// Information needed to execute an item. -struct executable { - struct menu *menu; - char *name; -}; - -// Executes an item with an activation token. -static void execute(struct executable *exe, const char *token) { - menu_destroy(exe->menu); - - setenv("XDG_ACTIVATION_TOKEN", token, true); - execlp(exe->name, exe->name, NULL); - - // Handle execution failure - fprintf(stderr, "Failed to execute selection: %s\n", strerror(errno)); - free(exe->name); - free(exe); - exit(EXIT_FAILURE); -} - -static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, - const char *token) { - struct executable *exe = data; - xdg_activation_token_v1_destroy(activation_token); - execute(exe, token); -} - -static const struct xdg_activation_token_v1_listener activation_token_listener = { - .done = activation_token_done, -}; - -// Executes the selected item. -static void menu_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_token_v1 *activation_token = xdg_activation_v1_get_activation_token(menu->activation); - xdg_activation_token_v1_set_surface(activation_token, menu->surface); - xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, exe); - xdg_activation_token_v1_commit(activation_token); -} - // Handle a keypress. void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, xkb_keysym_t sym) { @@ -639,8 +589,6 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, puts(menu->input); fflush(stdout); menu->exit = true; - } else if (menu->exec) { - menu_exec(menu); } else { char *text = menu->sel ? menu->sel->text : menu->input; puts(text); diff --git a/menu.h b/menu.h index 762dfc8..0143405 100644 --- a/menu.h +++ b/menu.h @@ -56,8 +56,6 @@ struct menu { int (*strncmp)(const char *, const char *, size_t); // Whether the input is a password bool passwd; - // Whether to execute the selected item - bool exec; // The font used to display the menu char *font; // The number of lines to list items vertically diff --git a/wmenu_run b/wmenu_run index afe96e2..b243b38 100755 --- a/wmenu_run +++ b/wmenu_run @@ -32,4 +32,4 @@ path() { fi } -path | wmenu -x "$@" +path | wmenu "$@" | ${SHELL:-"/bin/sh"} & From 6284eea24b44d05260f96fe842fa9dd752185942 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 17:03:07 -0400 Subject: [PATCH 47/72] Separate menu state from Wayland state --- main.c | 329 ++--------------------------------- menu.c | 179 +++++-------------- menu.h | 51 +----- meson.build | 1 + render.c | 16 +- wayland.c | 492 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wayland.h | 17 ++ 7 files changed, 577 insertions(+), 508 deletions(-) create mode 100644 wayland.c create mode 100644 wayland.h diff --git a/main.c b/main.c index 710f75f..be21e33 100644 --- a/main.c +++ b/main.c @@ -1,329 +1,24 @@ #define _POSIX_C_SOURCE 200809L -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "menu.h" -#include "render.h" -#include "xdg-activation-v1-client-protocol.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "wayland.h" -static void noop() { - // Do nothing -} - -static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { - struct menu *menu = data; - menu->output = wl_output_get_user_data(wl_output); -} - -static const struct wl_surface_listener surface_listener = { - .enter = surface_enter, - .leave = noop, -}; - -static void layer_surface_configure(void *data, - struct zwlr_layer_surface_v1 *surface, - uint32_t serial, uint32_t width, uint32_t height) { - struct menu *menu = data; - menu->width = width; - menu->height = height; - zwlr_layer_surface_v1_ack_configure(surface, serial); -} - -static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { - struct menu *menu = data; - menu->exit = true; -} - -static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = layer_surface_configure, - .closed = layer_surface_closed, -}; - -static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { - struct output *output = data; - output->scale = factor; -} - -static void output_name(void *data, struct wl_output *wl_output, const char *name) { - struct output *output = data; - output->name = name; - - struct menu *menu = output->menu; - if (menu->output_name && strcmp(menu->output_name, name) == 0) { - menu->output = output; +static void read_items(struct menu *menu) { + char buf[sizeof menu->input]; + while (fgets(buf, sizeof buf, stdin)) { + char *p = strchr(buf, '\n'); + if (p) { + *p = '\0'; + } + menu_add_item(menu, strdup(buf)); } } -static const struct wl_output_listener output_listener = { - .geometry = noop, - .mode = noop, - .done = noop, - .scale = output_scale, - .name = output_name, - .description = noop, -}; - -static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, - uint32_t format, int32_t fd, uint32_t size) { - struct keyboard *keyboard = data; - assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); - - char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - assert(map_shm != MAP_FAILED); - - keyboard->keymap = xkb_keymap_new_from_string(keyboard->context, - map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); - munmap(map_shm, size); - close(fd); - - keyboard->state = xkb_state_new(keyboard->keymap); -} - -static void keyboard_repeat(struct keyboard *keyboard) { - menu_keypress(keyboard->menu, keyboard->repeat_key_state, keyboard->repeat_sym); - struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = keyboard->repeat_period / 1000; - spec.it_value.tv_nsec = (keyboard->repeat_period % 1000) * 1000000l; - timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); -} - -static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { - struct keyboard *keyboard = data; - - enum wl_keyboard_key_state key_state = _key_state; - xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->state, key + 8); - menu_keypress(keyboard->menu, key_state, sym); - - if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && keyboard->repeat_period >= 0) { - keyboard->repeat_key_state = key_state; - keyboard->repeat_sym = sym; - - struct itimerspec spec = { 0 }; - spec.it_value.tv_sec = keyboard->repeat_delay / 1000; - spec.it_value.tv_nsec = (keyboard->repeat_delay % 1000) * 1000000l; - timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); - } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { - struct itimerspec spec = { 0 }; - timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); - } -} - -static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, - int32_t rate, int32_t delay) { - struct keyboard *keyboard = data; - keyboard->repeat_delay = delay; - if (rate > 0) { - keyboard->repeat_period = 1000 / rate; - } else { - keyboard->repeat_period = -1; - } -} - -static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) { - struct keyboard *keyboard = data; - xkb_state_update_mask(keyboard->state, mods_depressed, mods_latched, - mods_locked, 0, 0, group); -} - -static const struct wl_keyboard_listener keyboard_listener = { - .keymap = keyboard_keymap, - .enter = noop, - .leave = noop, - .key = keyboard_key, - .modifiers = keyboard_modifiers, - .repeat_info = keyboard_repeat_info, -}; - -static void seat_capabilities(void *data, struct wl_seat *seat, - enum wl_seat_capability caps) { - struct menu *menu = data; - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(seat); - struct keyboard *keyboard = keyboard_create(menu, wl_keyboard); - wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, keyboard); - menu_set_keyboard(menu, keyboard); - } -} - -static const struct wl_seat_listener seat_listener = { - .capabilities = seat_capabilities, - .name = noop, -}; - -static void data_device_selection(void *data, struct wl_data_device *data_device, - struct wl_data_offer *data_offer) { - struct menu *menu = data; - menu->data_offer = data_offer; -} - -static const struct wl_data_device_listener data_device_listener = { - .data_offer = noop, - .enter = noop, - .leave = noop, - .motion = noop, - .drop = noop, - .selection = data_device_selection, -}; - -static void handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) { - struct menu *menu = data; - if (strcmp(interface, wl_compositor_interface.name) == 0) { - menu->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); - } else if (strcmp(interface, wl_shm_interface.name) == 0) { - menu->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - menu->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); - wl_seat_add_listener(menu->seat, &seat_listener, menu); - } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { - menu->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); - } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - menu->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, 4); - struct output *output = output_create(menu, wl_output); - wl_output_set_user_data(wl_output, output); - wl_output_add_listener(wl_output, &output_listener, output); - menu_add_output(menu, output); - } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { - menu->activation = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); - } -} - -static const struct wl_registry_listener registry_listener = { - .global = handle_global, - .global_remove = noop, -}; - -// Connect to the Wayland display. -static void menu_connect(struct menu *menu) { - menu->display = wl_display_connect(NULL); - if (!menu->display) { - fprintf(stderr, "Failed to connect to display.\n"); - exit(EXIT_FAILURE); - } - - struct wl_registry *registry = wl_display_get_registry(menu->display); - wl_registry_add_listener(registry, ®istry_listener, menu); - wl_display_roundtrip(menu->display); - assert(menu->compositor != NULL); - assert(menu->shm != NULL); - assert(menu->seat != NULL); - assert(menu->data_device_manager != NULL); - assert(menu->layer_shell != NULL); - assert(menu->activation != NULL); - menu->registry = registry; - - // Get data device for seat - struct wl_data_device *data_device = wl_data_device_manager_get_data_device( - menu->data_device_manager, menu->seat); - wl_data_device_add_listener(data_device, &data_device_listener, menu); - menu->data_device = data_device; - - // Second roundtrip for seat and output listeners - wl_display_roundtrip(menu->display); - assert(menu->keyboard != NULL); - - if (menu->output_name && !menu->output) { - fprintf(stderr, "Output %s not found\n", menu->output_name); - exit(EXIT_FAILURE); - } - - menu->surface = wl_compositor_create_surface(menu->compositor); - wl_surface_add_listener(menu->surface, &surface_listener, menu); - - struct zwlr_layer_surface_v1 *layer_surface = zwlr_layer_shell_v1_get_layer_surface( - menu->layer_shell, - menu->surface, - menu->output ? menu->output->output : NULL, - ZWLR_LAYER_SHELL_V1_LAYER_TOP, - "menu" - ); - assert(layer_surface != NULL); - menu->layer_surface = layer_surface; - - uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if (menu->bottom) { - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - } else { - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - } - - zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); - zwlr_layer_surface_v1_set_size(layer_surface, 0, menu->height); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, true); - zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, menu); - - wl_surface_commit(menu->surface); - wl_display_roundtrip(menu->display); -} - int main(int argc, char *argv[]) { struct menu *menu = menu_create(); menu_getopts(menu, argc, argv); - menu_connect(menu); - render_menu(menu); - - read_menu_items(menu); - render_menu(menu); - - struct pollfd fds[] = { - { wl_display_get_fd(menu->display), POLLIN }, - { menu->keyboard->repeat_timer, POLLIN }, - }; - const size_t nfds = sizeof(fds) / sizeof(*fds); - - while (!menu->exit) { - errno = 0; - do { - if (wl_display_flush(menu->display) == -1 && errno != EAGAIN) { - fprintf(stderr, "wl_display_flush: %s\n", strerror(errno)); - break; - } - } while (errno == EAGAIN); - - if (poll(fds, nfds, -1) < 0) { - fprintf(stderr, "poll: %s\n", strerror(errno)); - break; - } - - if (fds[0].revents & POLLIN) { - if (wl_display_dispatch(menu->display) < 0) { - menu->exit = true; - } - } - - if (fds[1].revents & POLLIN) { - keyboard_repeat(menu->keyboard); - } + if (!menu->passwd) { + read_items(menu); } - - bool failure = menu->failure; - menu_destroy(menu); - - if (failure) { - return EXIT_FAILURE; - } - return EXIT_SUCCESS; + return menu_run(menu); } diff --git a/menu.c b/menu.c index 72d903f..eecf1a0 100644 --- a/menu.c +++ b/menu.c @@ -1,5 +1,4 @@ #define _POSIX_C_SOURCE 200809L -#include #include #include #include @@ -19,10 +18,8 @@ #include "menu.h" #include "pango.h" -#include "pool-buffer.h" #include "render.h" -#include "xdg-activation-v1-client-protocol.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "wayland.h" // Creates and returns a new menu. struct menu *menu_create() { @@ -38,38 +35,6 @@ struct menu *menu_create() { return menu; } -// Creates and returns a new keyboard. -struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard) { - struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); - keyboard->menu = menu; - keyboard->keyboard = wl_keyboard; - keyboard->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - assert(keyboard->context != NULL); - keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); - assert(keyboard->repeat_timer != -1); - return keyboard; -} - -// Sets the current keyboard. -void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard) { - menu->keyboard = keyboard; -} - -// Creates and returns a new output. -struct output *output_create(struct menu *menu, struct wl_output *wl_output) { - struct output *output = calloc(1, sizeof(struct output)); - output->menu = menu; - output->output = wl_output; - output->scale = 1; - return output; -} - -// Adds an output to the output list. -void menu_add_output(struct menu *menu, struct output *output) { - output->next = menu->output_list; - menu->output_list = output; -} - static bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; @@ -170,6 +135,22 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) { menu->padding = height / 2; } +// Add an item to the menu. +void menu_add_item(struct menu *menu, char *text) { + struct item *item = calloc(1, sizeof *item); + if (!item) { + return; + } + item->text = text; + + if (menu->lastitem) { + menu->lastitem->next = item; + } else { + menu->items = item; + } + menu->lastitem = item; +} + static void append_page(struct page *page, struct page **first, struct page **last) { if (*last) { (*last)->next = page; @@ -246,7 +227,7 @@ static const char *fstrstr(struct menu *menu, const char *s, const char *sub) { return NULL; } -static void append_item(struct item *item, struct item **first, struct item **last) { +static void append_match(struct item *item, struct item **first, struct item **last) { if (*last) { (*last)->next_match = item; } else { @@ -300,11 +281,11 @@ static void match_items(struct menu *menu) { continue; } if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) { - append_item(item, &lexact, &exactend); + append_match(item, &lexact, &exactend); } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { - append_item(item, &lprefix, &prefixend); + append_match(item, &lprefix, &prefixend); } else { - append_item(item, &lsubstr, &substrend); + append_match(item, &lsubstr, &substrend); } } @@ -339,45 +320,27 @@ static void match_items(struct menu *menu) { } } -// Read menu items from standard input. -void read_menu_items(struct menu *menu) { - if (menu->passwd) { - // Don't read standard input in password mode - calc_widths(menu); - return; - } - - char buf[sizeof menu->input]; - struct item **next = &menu->items; - while (fgets(buf, sizeof buf, stdin)) { - char *p = strchr(buf, '\n'); - if (p) { - *p = '\0'; - } - struct item *item = calloc(1, sizeof *item); - if (!item) { - return; - } - item->text = strdup(buf); - - *next = item; - next = &item->next; - } - +// Process menu items. +void menu_process_items(struct menu *menu) { calc_widths(menu); match_items(menu); } -static void insert(struct menu *menu, const char *s, ssize_t n) { - if (strlen(menu->input) + n > sizeof menu->input - 1) { +static void insert(struct menu *menu, const char *text, ssize_t len) { + if (strlen(menu->input) + len > sizeof menu->input - 1) { return; } - memmove(menu->input + menu->cursor + n, menu->input + menu->cursor, - sizeof menu->input - menu->cursor - MAX(n, 0)); - if (n > 0 && s != NULL) { - memcpy(menu->input + menu->cursor, s, n); + memmove(menu->input + menu->cursor + len, menu->input + menu->cursor, + sizeof menu->input - menu->cursor - MAX(len, 0)); + if (len > 0 && text != NULL) { + memcpy(menu->input + menu->cursor, text, len); } - menu->cursor += n; + menu->cursor += len; +} + +// Add pasted text to the menu input. +void menu_paste(struct menu *menu, const char *text, ssize_t len) { + insert(menu, text, len); } static size_t nextrune(struct menu *menu, int incr) { @@ -417,14 +380,12 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; } - bool ctrl = xkb_state_mod_name_is_active(menu->keyboard->state, - XKB_MOD_NAME_CTRL, + struct xkb_state *state = context_get_xkb_state(menu->context); + bool ctrl = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool meta = xkb_state_mod_name_is_active(menu->keyboard->state, - XKB_MOD_NAME_ALT, + bool meta = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); - bool shift = xkb_state_mod_name_is_active(menu->keyboard->state, - XKB_MOD_NAME_SHIFT, + bool shift = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); size_t len = strlen(menu->input); @@ -501,32 +462,9 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, return; case XKB_KEY_Y: // Paste clipboard - if (!menu->data_offer) { + if (!context_paste(menu->context)) { return; } - - int fds[2]; - if (pipe(fds) == -1) { - // Pipe failed - return; - } - wl_data_offer_receive(menu->data_offer, "text/plain", fds[1]); - close(fds[1]); - - wl_display_roundtrip(menu->display); - - while (true) { - char buf[1024]; - ssize_t n = read(fds[0], buf, sizeof(buf)); - if (n <= 0) { - break; - } - insert(menu, buf, n); - } - close(fds[0]); - - wl_data_offer_destroy(menu->data_offer); - menu->data_offer = NULL; match_items(menu); render_menu(menu); return; @@ -696,26 +634,6 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, } } -// Frees the keyboard. -static void free_keyboard(struct keyboard *keyboard) { - wl_keyboard_release(keyboard->keyboard); - xkb_state_unref(keyboard->state); - xkb_keymap_unref(keyboard->keymap); - xkb_context_unref(keyboard->context); - free(keyboard); -} - -// Frees the outputs. -static void free_outputs(struct menu *menu) { - struct output *next = menu->output_list; - while (next) { - struct output *output = next; - next = output->next; - wl_output_destroy(output->output); - free(output); - } -} - // Frees menu pages. static void free_pages(struct menu *menu) { struct page *next = menu->pages; @@ -739,26 +657,9 @@ static void free_items(struct menu *menu) { // Destroys the menu, freeing memory associated with it. void menu_destroy(struct menu *menu) { - wl_registry_destroy(menu->registry); - wl_compositor_destroy(menu->compositor); - wl_shm_destroy(menu->shm); - wl_seat_destroy(menu->seat); - wl_data_device_manager_destroy(menu->data_device_manager); - zwlr_layer_shell_v1_destroy(menu->layer_shell); - free_outputs(menu); - - free_keyboard(menu->keyboard); - wl_data_device_destroy(menu->data_device); - wl_surface_destroy(menu->surface); - zwlr_layer_surface_v1_destroy(menu->layer_surface); - xdg_activation_v1_destroy(menu->activation); - free_pages(menu); free_items(menu); destroy_buffer(&menu->buffers[0]); destroy_buffer(&menu->buffers[1]); - - wl_display_disconnect(menu->display); - free(menu); } diff --git a/menu.h b/menu.h index 0143405..6afdd7e 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,6 @@ #include #include "pool-buffer.h" -#include "xdg-activation-v1-client-protocol.h" // A menu item. struct item { @@ -24,30 +23,6 @@ struct page { struct page *next; // next page }; -// A Wayland output. -struct output { - struct menu *menu; - struct wl_output *output; - const char *name; // output name - int32_t scale; // output scale - struct output *next; // next output -}; - -// Keyboard state. -struct keyboard { - struct menu *menu; - struct wl_keyboard *keyboard; - struct xkb_context *context; - struct xkb_keymap *keymap; - struct xkb_state *state; - - int repeat_timer; - int repeat_delay; - int repeat_period; - enum wl_keyboard_key_state repeat_key_state; - xkb_keysym_t repeat_sym; -}; - // Menu state. struct menu { // Whether the menu appears at the bottom of the screen @@ -71,22 +46,7 @@ struct menu { // Selection colors uint32_t selectionbg, selectionfg; - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shm *shm; - struct wl_seat *seat; - struct wl_data_device_manager *data_device_manager; - struct zwlr_layer_shell_v1 *layer_shell; - struct output *output_list; - struct xdg_activation_v1 *activation; - - struct keyboard *keyboard; - struct wl_data_device *data_device; - struct wl_surface *surface; - struct zwlr_layer_surface_v1 *layer_surface; - struct wl_data_offer *data_offer; - struct output *output; + struct wl_context *context; struct pool_buffer buffers[2]; struct pool_buffer *current; @@ -104,6 +64,7 @@ struct menu { size_t cursor; 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 @@ -114,12 +75,10 @@ struct menu { }; struct menu *menu_create(); -struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard); -void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard); -struct output *output_create(struct menu *menu, struct wl_output *wl_output); -void menu_add_output(struct menu *menu, struct output *output); void menu_getopts(struct menu *menu, int argc, char *argv[]); -void read_menu_items(struct menu *menu); +void menu_add_item(struct menu *menu, char *text); +void menu_process_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, xkb_keysym_t sym); void menu_destroy(struct menu *menu); diff --git a/meson.build b/meson.build index 56fe511..fc4a2c0 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ executable( 'pango.c', 'pool-buffer.c', 'render.c', + 'wayland.c', ), dependencies: [ cairo, diff --git a/render.c b/render.c index a0a2a8c..7142518 100644 --- a/render.c +++ b/render.c @@ -7,6 +7,7 @@ #include "menu.h" #include "pango.h" +#include "wayland.h" // Calculate text widths. void calc_widths(struct menu *menu) { @@ -177,6 +178,8 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) { // Renders a single frame of the menu. 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); @@ -191,8 +194,8 @@ void render_menu(struct menu *menu) { render_to_cairo(menu, cairo); - int scale = menu->output ? menu->output->scale : 1; - menu->current = get_next_buffer(menu->shm, + int scale = context_get_scale(context); + menu->current = get_next_buffer(context_get_shm(context), menu->buffers, menu->width, menu->height, scale); if (!menu->current) { goto cleanup; @@ -206,10 +209,11 @@ void render_menu(struct menu *menu) { cairo_set_source_surface(shm, recorder, 0, 0); cairo_paint(shm); - wl_surface_set_buffer_scale(menu->surface, scale); - wl_surface_attach(menu->surface, menu->current->buffer, 0, 0); - wl_surface_damage(menu->surface, 0, 0, menu->width, menu->height); - wl_surface_commit(menu->surface); + struct wl_surface *surface = context_get_surface(context); + wl_surface_set_buffer_scale(surface, scale); + wl_surface_attach(surface, menu->current->buffer, 0, 0); + wl_surface_damage(surface, 0, 0, menu->width, menu->height); + wl_surface_commit(surface); cleanup: cairo_destroy(cairo); diff --git a/wayland.c b/wayland.c new file mode 100644 index 0000000..efcad50 --- /dev/null +++ b/wayland.c @@ -0,0 +1,492 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "menu.h" +#include "render.h" +#include "wayland.h" +#include "xdg-activation-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +// A Wayland output. +struct output { + struct wl_context *context; + struct wl_output *output; + const char *name; // output name + int32_t scale; // output scale + struct output *next; // next output +}; + +// Creates and returns a new output. +static struct output *output_create(struct wl_context *context, struct wl_output *wl_output) { + struct output *output = calloc(1, sizeof(struct output)); + output->context = context; + output->output = wl_output; + output->scale = 1; + return output; +} + +// Keyboard state. +struct keyboard { + struct menu *menu; + struct wl_keyboard *keyboard; + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; + + int repeat_timer; + int repeat_delay; + int repeat_period; + enum wl_keyboard_key_state repeat_key_state; + xkb_keysym_t repeat_sym; +}; + +// Creates and returns a new keyboard. +static struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard) { + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard->menu = menu; + keyboard->keyboard = wl_keyboard; + keyboard->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + assert(keyboard->context != NULL); + keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); + assert(keyboard->repeat_timer != -1); + return keyboard; +} + +// Frees the keyboard. +static void free_keyboard(struct keyboard *keyboard) { + wl_keyboard_release(keyboard->keyboard); + xkb_state_unref(keyboard->state); + xkb_keymap_unref(keyboard->keymap); + xkb_context_unref(keyboard->context); + free(keyboard); +} + +// Wayland context. +struct wl_context { + struct menu *menu; + + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_data_device_manager *data_device_manager; + struct zwlr_layer_shell_v1 *layer_shell; + struct output *output_list; + struct xdg_activation_v1 *activation; + + struct keyboard *keyboard; + struct wl_data_device *data_device; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_data_offer *data_offer; + struct output *output; +}; + +// Returns the current output_scale. +int context_get_scale(struct wl_context *context) { + return context->output ? context->output->scale : 1; +} + +// Returns the shared memory for the context. +struct wl_shm *context_get_shm(struct wl_context *context) { + return context->shm; +} + +// Returns the Wayland surface for the context. +struct wl_surface *context_get_surface(struct wl_context *context) { + return context->surface; +} + +// Returns the XKB state for the context. +struct xkb_state *context_get_xkb_state(struct wl_context *context) { + return context->keyboard->state; +} + +// Retrieves pasted text from a Wayland data offer. +bool context_paste(struct wl_context *context) { + if (!context->data_offer) { + return false; + } + + int fds[2]; + if (pipe(fds) == -1) { + // Pipe failed + return false; + } + wl_data_offer_receive(context->data_offer, "text/plain", fds[1]); + close(fds[1]); + + wl_display_roundtrip(context->display); + + while (true) { + char buf[1024]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) { + break; + } + menu_paste(context->menu, buf, n); + } + close(fds[0]); + + wl_data_offer_destroy(context->data_offer); + context->data_offer = NULL; + return true; +} + +// Adds an output to the output list. +static void context_add_output(struct wl_context *context, struct output *output) { + output->next = context->output_list; + context->output_list = output; +} + +// Frees the outputs. +static void free_outputs(struct wl_context *context) { + struct output *next = context->output_list; + while (next) { + struct output *output = next; + next = output->next; + wl_output_destroy(output->output); + free(output); + } +} + +// Destroys the Wayland context, freeing memory associated with it. +static void context_destroy(struct wl_context *context) { + wl_registry_destroy(context->registry); + wl_compositor_destroy(context->compositor); + wl_shm_destroy(context->shm); + wl_seat_destroy(context->seat); + wl_data_device_manager_destroy(context->data_device_manager); + zwlr_layer_shell_v1_destroy(context->layer_shell); + free_outputs(context); + + free_keyboard(context->keyboard); + wl_data_device_destroy(context->data_device); + wl_surface_destroy(context->surface); + zwlr_layer_surface_v1_destroy(context->layer_surface); + xdg_activation_v1_destroy(context->activation); + + wl_display_disconnect(context->display); + free(context); +} + +static void noop() { + // Do nothing +} + +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); +} + +static const struct wl_surface_listener surface_listener = { + .enter = surface_enter, + .leave = noop, +}; + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + struct wl_context *context = data; + context->menu->width = width; + context->menu->height = height; + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + +static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { + struct wl_context *context = data; + context->menu->exit = true; +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { + struct output *output = data; + output->scale = factor; +} + +static void output_name(void *data, struct wl_output *wl_output, const char *name) { + struct output *output = data; + output->name = name; + + struct wl_context *context = output->context; + if (context->menu->output_name && strcmp(context->menu->output_name, name) == 0) { + context->output = output; + } +} + +static const struct wl_output_listener output_listener = { + .geometry = noop, + .mode = noop, + .done = noop, + .scale = output_scale, + .name = output_name, + .description = noop, +}; + +static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + struct keyboard *keyboard = data; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + assert(map_shm != MAP_FAILED); + + keyboard->keymap = xkb_keymap_new_from_string(keyboard->context, + map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); + munmap(map_shm, size); + close(fd); + + keyboard->state = xkb_state_new(keyboard->keymap); +} + +static void keyboard_repeat(struct keyboard *keyboard) { + menu_keypress(keyboard->menu, keyboard->repeat_key_state, keyboard->repeat_sym); + struct itimerspec spec = { 0 }; + spec.it_value.tv_sec = keyboard->repeat_period / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_period % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); +} + +static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { + struct keyboard *keyboard = data; + + enum wl_keyboard_key_state key_state = _key_state; + xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->state, key + 8); + menu_keypress(keyboard->menu, key_state, sym); + + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && keyboard->repeat_period >= 0) { + keyboard->repeat_key_state = key_state; + keyboard->repeat_sym = sym; + + struct itimerspec spec = { 0 }; + spec.it_value.tv_sec = keyboard->repeat_delay / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_delay % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); + } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { + struct itimerspec spec = { 0 }; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); + } +} + +static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) { + struct keyboard *keyboard = data; + keyboard->repeat_delay = delay; + if (rate > 0) { + keyboard->repeat_period = 1000 / rate; + } else { + keyboard->repeat_period = -1; + } +} + +static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + struct keyboard *keyboard = data; + xkb_state_update_mask(keyboard->state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_keymap, + .enter = noop, + .leave = noop, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = keyboard_repeat_info, +}; + +static void seat_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) { + struct wl_context *context = data; + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(seat); + struct keyboard *keyboard = keyboard_create(context->menu, wl_keyboard); + wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, keyboard); + context->keyboard = keyboard; + } +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_capabilities, + .name = noop, +}; + +static void data_device_selection(void *data, struct wl_data_device *data_device, + struct wl_data_offer *data_offer) { + struct wl_context *context = data; + context->data_offer = data_offer; +} + +static const struct wl_data_device_listener data_device_listener = { + .data_offer = noop, + .enter = noop, + .leave = noop, + .motion = noop, + .drop = noop, + .selection = data_device_selection, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct wl_context *context = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + context->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + context->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + context->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); + wl_seat_add_listener(context->seat, &seat_listener, data); + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + context->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + context->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, 4); + struct output *output = output_create(context, wl_output); + wl_output_set_user_data(wl_output, output); + wl_output_add_listener(wl_output, &output_listener, output); + context_add_output(context, output); + } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + context->activation = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = noop, +}; + +// Connect to the Wayland display and run the menu. +int menu_run(struct menu *menu) { + struct wl_context *context = calloc(1, sizeof(struct wl_context)); + context->menu = menu; + menu->context = context; + + context->display = wl_display_connect(NULL); + if (!context->display) { + fprintf(stderr, "Failed to connect to display.\n"); + exit(EXIT_FAILURE); + } + + struct wl_registry *registry = wl_display_get_registry(context->display); + wl_registry_add_listener(registry, ®istry_listener, context); + wl_display_roundtrip(context->display); + assert(context->compositor != NULL); + assert(context->shm != NULL); + assert(context->seat != NULL); + assert(context->data_device_manager != NULL); + assert(context->layer_shell != NULL); + assert(context->activation != NULL); + context->registry = registry; + + // Get data device for seat + struct wl_data_device *data_device = wl_data_device_manager_get_data_device( + context->data_device_manager, context->seat); + wl_data_device_add_listener(data_device, &data_device_listener, context); + context->data_device = data_device; + + // Second roundtrip for seat and output listeners + wl_display_roundtrip(context->display); + assert(context->keyboard != NULL); + + if (menu->output_name && !context->output) { + fprintf(stderr, "Output %s not found\n", menu->output_name); + exit(EXIT_FAILURE); + } + + context->surface = wl_compositor_create_surface(context->compositor); + wl_surface_add_listener(context->surface, &surface_listener, context); + + struct zwlr_layer_surface_v1 *layer_surface = zwlr_layer_shell_v1_get_layer_surface( + context->layer_shell, + context->surface, + context->output ? context->output->output : NULL, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + "menu" + ); + assert(layer_surface != NULL); + context->layer_surface = layer_surface; + + uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (menu->bottom) { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + } else { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + } + + zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); + zwlr_layer_surface_v1_set_size(layer_surface, 0, menu->height); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, true); + zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, context); + + wl_surface_commit(context->surface); + wl_display_roundtrip(context->display); + render_menu(menu); + menu_process_items(menu); + render_menu(menu); + + struct pollfd fds[] = { + { wl_display_get_fd(context->display), POLLIN }, + { context->keyboard->repeat_timer, POLLIN }, + }; + const size_t nfds = sizeof(fds) / sizeof(*fds); + + while (!menu->exit) { + errno = 0; + do { + if (wl_display_flush(context->display) == -1 && errno != EAGAIN) { + fprintf(stderr, "wl_display_flush: %s\n", strerror(errno)); + break; + } + } while (errno == EAGAIN); + + if (poll(fds, nfds, -1) < 0) { + fprintf(stderr, "poll: %s\n", strerror(errno)); + break; + } + + if (fds[0].revents & POLLIN) { + if (wl_display_dispatch(context->display) < 0) { + menu->exit = true; + } + } + + if (fds[1].revents & POLLIN) { + keyboard_repeat(context->keyboard); + } + } + + bool failure = menu->failure; + menu_destroy(menu); + context_destroy(context); + + if (failure) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/wayland.h b/wayland.h new file mode 100644 index 0000000..bca663f --- /dev/null +++ b/wayland.h @@ -0,0 +1,17 @@ +#ifndef WMENU_WAYLAND_H +#define WMENU_WAYLAND_H + +#include "menu.h" +#include + +struct wl_context; + +int menu_run(struct menu *menu); + +int context_get_scale(struct wl_context *context); +struct wl_shm *context_get_shm(struct wl_context *context); +struct wl_surface *context_get_surface(struct wl_context *context); +struct xkb_state *context_get_xkb_state(struct wl_context *context); +bool context_paste(struct wl_context *context); + +#endif From 1f221a73cf290ff509ef6c066ff692bb48f8625e Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 18:45:49 -0400 Subject: [PATCH 48/72] Fix destruction of pool buffers --- main.c | 6 +++++- menu.c | 10 +++++----- menu.h | 10 ++++------ render.c | 14 ++++++++------ wayland.c | 24 ++++++++++++++++-------- wayland.h | 3 ++- 6 files changed, 40 insertions(+), 27 deletions(-) diff --git a/main.c b/main.c index be21e33..7eae947 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,7 @@ #define _POSIX_C_SOURCE 200809L +#include + #include "menu.h" #include "wayland.h" @@ -20,5 +22,7 @@ int main(int argc, char *argv[]) { if (!menu->passwd) { read_items(menu); } - return menu_run(menu); + int status = menu_run(menu); + menu_destroy(menu); + return status; } diff --git a/menu.c b/menu.c index eecf1a0..144a052 100644 --- a/menu.c +++ b/menu.c @@ -320,10 +320,12 @@ static void match_items(struct menu *menu) { } } -// Process menu items. -void menu_process_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); } static void insert(struct menu *menu, const char *text, ssize_t len) { @@ -659,7 +661,5 @@ static void free_items(struct menu *menu) { void menu_destroy(struct menu *menu) { free_pages(menu); free_items(menu); - - destroy_buffer(&menu->buffers[0]); - destroy_buffer(&menu->buffers[1]); + free(menu); } diff --git a/menu.h b/menu.h index 6afdd7e..bfbc94a 100644 --- a/menu.h +++ b/menu.h @@ -1,9 +1,10 @@ #ifndef WMENU_MENU_H #define WMENU_MENU_H +#include +#include #include - -#include "pool-buffer.h" +#include // A menu item. struct item { @@ -48,9 +49,6 @@ struct menu { struct wl_context *context; - struct pool_buffer buffers[2]; - struct pool_buffer *current; - int width; int height; int line_height; @@ -77,7 +75,7 @@ struct menu { struct menu *menu_create(); void menu_getopts(struct menu *menu, int argc, char *argv[]); void menu_add_item(struct menu *menu, char *text); -void menu_process_items(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, xkb_keysym_t sym); diff --git a/render.c b/render.c index 7142518..3813af5 100644 --- a/render.c +++ b/render.c @@ -7,11 +7,14 @@ #include "menu.h" #include "pango.h" +#include "pool-buffer.h" #include "wayland.h" // Calculate text widths. void calc_widths(struct menu *menu) { - cairo_t *cairo = menu->current->cairo; + struct wl_context *context = menu->context; + struct pool_buffer *current = context_get_current_buffer(context); + cairo_t *cairo = current->cairo; // Calculate prompt width if (menu->prompt) { @@ -195,13 +198,12 @@ void render_menu(struct menu *menu) { render_to_cairo(menu, cairo); int scale = context_get_scale(context); - menu->current = get_next_buffer(context_get_shm(context), - menu->buffers, menu->width, menu->height, scale); - if (!menu->current) { + struct pool_buffer *buffer = context_get_next_buffer(context, scale); + if (!buffer) { goto cleanup; } - cairo_t *shm = menu->current->cairo; + cairo_t *shm = buffer->cairo; cairo_save(shm); cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR); cairo_paint(shm); @@ -211,7 +213,7 @@ void render_menu(struct menu *menu) { struct wl_surface *surface = context_get_surface(context); wl_surface_set_buffer_scale(surface, scale); - wl_surface_attach(surface, menu->current->buffer, 0, 0); + wl_surface_attach(surface, buffer->buffer, 0, 0); wl_surface_damage(surface, 0, 0, menu->width, menu->height); wl_surface_commit(surface); diff --git a/wayland.c b/wayland.c index efcad50..1671e6f 100644 --- a/wayland.c +++ b/wayland.c @@ -17,7 +17,7 @@ #include #include "menu.h" -#include "render.h" +#include "pool-buffer.h" #include "wayland.h" #include "xdg-activation-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -96,6 +96,9 @@ struct wl_context { struct zwlr_layer_surface_v1 *layer_surface; struct wl_data_offer *data_offer; struct output *output; + + struct pool_buffer buffers[2]; + struct pool_buffer *current; }; // Returns the current output_scale. @@ -103,9 +106,16 @@ int context_get_scale(struct wl_context *context) { return context->output ? context->output->scale : 1; } -// Returns the shared memory for the context. -struct wl_shm *context_get_shm(struct wl_context *context) { - return context->shm; +// Returns the current buffer from the pool. +struct pool_buffer *context_get_current_buffer(struct wl_context *context) { + return context->current; +} + +// Returns the next buffer from the pool. +struct pool_buffer *context_get_next_buffer(struct wl_context *context, int scale) { + struct menu *menu = context->menu; + context->current = get_next_buffer(context->shm, context->buffers, menu->width, menu->height, scale); + return context->current; } // Returns the Wayland surface for the context. @@ -446,9 +456,7 @@ int menu_run(struct menu *menu) { wl_surface_commit(context->surface); wl_display_roundtrip(context->display); - render_menu(menu); - menu_process_items(menu); - render_menu(menu); + menu_render_items(menu); struct pollfd fds[] = { { wl_display_get_fd(context->display), POLLIN }, @@ -482,8 +490,8 @@ int menu_run(struct menu *menu) { } bool failure = menu->failure; - menu_destroy(menu); context_destroy(context); + menu->context = NULL; if (failure) { return EXIT_FAILURE; diff --git a/wayland.h b/wayland.h index bca663f..dbbcfdf 100644 --- a/wayland.h +++ b/wayland.h @@ -9,7 +9,8 @@ struct wl_context; int menu_run(struct menu *menu); int context_get_scale(struct wl_context *context); -struct wl_shm *context_get_shm(struct wl_context *context); +struct pool_buffer *context_get_current_buffer(struct wl_context *context); +struct pool_buffer *context_get_next_buffer(struct wl_context *context, int scale); struct wl_surface *context_get_surface(struct wl_context *context); struct xkb_state *context_get_xkb_state(struct wl_context *context); bool context_paste(struct wl_context *context); From 41e8599392a543a537f15447e20fd7bc8d8f2297 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 21:39:54 -0400 Subject: [PATCH 49/72] Add wmenu-run executable --- menu.c | 2 ++ menu.h | 1 + meson.build | 25 ++++++++++++++++- wayland.c | 5 ++++ wayland.h | 1 + wmenu-run.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ main.c => wmenu.c | 0 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 wmenu-run.c rename main.c => wmenu.c (100%) diff --git a/menu.c b/menu.c index 144a052..cbc94b4 100644 --- a/menu.c +++ b/menu.c @@ -529,6 +529,8 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, 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; puts(text); diff --git a/menu.h b/menu.h index bfbc94a..039cac7 100644 --- a/menu.h +++ b/menu.h @@ -68,6 +68,7 @@ struct menu { struct item *sel; // selected item struct page *pages; // list of pages + void (*callback)(struct menu *menu); bool exit; bool failure; }; diff --git a/meson.build b/meson.build index fc4a2c0..552e1f8 100644 --- a/meson.build +++ b/meson.build @@ -38,12 +38,35 @@ install_data('wmenu_run', install_dir: get_option('bindir')) executable( 'wmenu', files( - 'main.c', 'menu.c', 'pango.c', 'pool-buffer.c', 'render.c', 'wayland.c', + 'wmenu.c', + ), + dependencies: [ + cairo, + client_protos, + pango, + pangocairo, + rt, + wayland_client, + wayland_protos, + xkbcommon, + ], + install: true, +) + +executable( + 'wmenu-run', + files( + 'menu.c', + 'pango.c', + 'pool-buffer.c', + 'render.c', + 'wayland.c', + 'wmenu-run.c', ), dependencies: [ cairo, diff --git a/wayland.c b/wayland.c index 1671e6f..d31ca20 100644 --- a/wayland.c +++ b/wayland.c @@ -128,6 +128,11 @@ struct xkb_state *context_get_xkb_state(struct wl_context *context) { return context->keyboard->state; } +// Returns the XDG activation object for the context. +struct xdg_activation_v1 *context_get_xdg_activation(struct wl_context *context) { + return context->activation; +} + // Retrieves pasted text from a Wayland data offer. bool context_paste(struct wl_context *context) { if (!context->data_offer) { diff --git a/wayland.h b/wayland.h index dbbcfdf..c5d7ce5 100644 --- a/wayland.h +++ b/wayland.h @@ -13,6 +13,7 @@ struct pool_buffer *context_get_current_buffer(struct wl_context *context); struct pool_buffer *context_get_next_buffer(struct wl_context *context, int scale); struct wl_surface *context_get_surface(struct wl_context *context); struct xkb_state *context_get_xkb_state(struct wl_context *context); +struct xdg_activation_v1 *context_get_xdg_activation(struct wl_context *context); bool context_paste(struct wl_context *context); #endif diff --git a/wmenu-run.c b/wmenu-run.c new file mode 100644 index 0000000..fe79ece --- /dev/null +++ b/wmenu-run.c @@ -0,0 +1,70 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include + +#include "menu.h" +#include "wayland.h" +#include "xdg-activation-v1-client-protocol.h" + +static void read_items(struct menu *menu) { + char buf[sizeof menu->input]; + while (fgets(buf, sizeof buf, stdin)) { + char *p = strchr(buf, '\n'); + if (p) { + *p = '\0'; + } + menu_add_item(menu, strdup(buf)); + } +} + +struct executable { + struct menu *menu; + char *name; +}; + +static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, + const char *token) { + struct executable *exe = data; + xdg_activation_token_v1_destroy(activation_token); + menu_destroy(exe->menu); + + 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(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, exe); + xdg_activation_token_v1_commit(activation_token); +} + +int main(int argc, char *argv[]) { + struct menu *menu = menu_create(); + menu->callback = exec; + menu_getopts(menu, argc, argv); + read_items(menu); + int status = menu_run(menu); + menu_destroy(menu); + return status; +} diff --git a/main.c b/wmenu.c similarity index 100% rename from main.c rename to wmenu.c From 477c0419b4a06b286bf09405e44c5fdc36e44b2f Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 21:40:46 -0400 Subject: [PATCH 50/72] Remove wmenu_run script --- meson.build | 2 -- wmenu_run | 35 ----------------------------------- 2 files changed, 37 deletions(-) delete mode 100755 wmenu_run diff --git a/meson.build b/meson.build index 552e1f8..6a745aa 100644 --- a/meson.build +++ b/meson.build @@ -33,8 +33,6 @@ rt = cc.find_library('rt') subdir('protocols') subdir('docs') -install_data('wmenu_run', install_dir: get_option('bindir')) - executable( 'wmenu', files( diff --git a/wmenu_run b/wmenu_run deleted file mode 100755 index b243b38..0000000 --- a/wmenu_run +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" -cache="$cachedir/wmenu_run" - -[ -d "$cachedir" ] || mkdir -p "$cachedir" - -uptodate() { - [ -f "$cache" ] || return 1 - IFS=: - for path in $PATH; do - # non-POSIX - test "$path" -nt "$cache" && return 1 - done - return 0 -} - -bins() { - IFS=: - for path in $PATH; do - for bin in "$path"/*; do - [ -x "$bin" ] && echo "${bin##*/}" - done - done -} - -path() { - if uptodate; then - cat "$cache" - else - bins | sort -u | tee "$cache" - fi -} - -path | wmenu "$@" | ${SHELL:-"/bin/sh"} & From 92d3b294aeff5b36916782847bae4389c10e8f17 Mon Sep 17 00:00:00 2001 From: adnano Date: Thu, 2 May 2024 21:41:26 -0400 Subject: [PATCH 51/72] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0e62c4..649e020 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ See wmenu(1) To use wmenu with Sway, you can add the following to your configuration file: ``` -set $menu dmenu_path | wmenu | xargs swaymsg exec -- +set $menu dmenu_path | wmenu-run bindsym $mod+d exec $menu ``` From 8f19d6a8d2f34aeb4060d4374eb204b270ffbaa8 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 3 May 2024 19:10:28 -0400 Subject: [PATCH 52/72] wmenu-run: Populate items from PATH --- menu.c | 87 ++++++++++++++++++++++++++++++++--------------------- menu.h | 4 +-- wmenu-run.c | 19 ++++++++---- wmenu.c | 2 +- 4 files changed, 68 insertions(+), 44 deletions(-) diff --git a/menu.c b/menu.c index cbc94b4..baa3061 100644 --- a/menu.c +++ b/menu.c @@ -35,6 +35,36 @@ struct menu *menu_create() { return menu; } +static void free_pages(struct menu *menu) { + struct page *next = menu->pages; + while (next) { + struct page *page = next; + next = page->next; + free(page); + } +} + +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); + } +} + +// Destroys the menu, freeing memory associated with it. +void menu_destroy(struct menu *menu) { + free_pages(menu); + free_items(menu); + free(menu); +} + static bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; @@ -136,19 +166,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) { - struct item *item = calloc(1, sizeof *item); - if (!item) { +void menu_add_item(struct menu *menu, char *text, bool sort) { + struct item *new = calloc(1, sizeof(struct item)); + if (!new) { return; } - item->text = text; + 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; + } + } + } if (menu->lastitem) { - menu->lastitem->next = item; + menu->lastitem->next = new; } else { - menu->items = item; + menu->items = new; } - menu->lastitem = item; + menu->lastitem = new; } static void append_page(struct page *page, struct page **first, struct page **last) { @@ -637,31 +682,3 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, } } } - -// Frees menu pages. -static void free_pages(struct menu *menu) { - struct page *next = menu->pages; - while (next) { - struct page *page = next; - next = page->next; - free(page); - } -} - -// Frees menu items. -static void free_items(struct menu *menu) { - struct item *next = menu->items; - while (next) { - struct item *item = next; - next = item->next; - free(item->text); - free(item); - } -} - -// Destroys the menu, freeing memory associated with it. -void menu_destroy(struct menu *menu) { - free_pages(menu); - free_items(menu); - free(menu); -} diff --git a/menu.h b/menu.h index 039cac7..724aa53 100644 --- a/menu.h +++ b/menu.h @@ -74,12 +74,12 @@ struct menu { }; 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_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, xkb_keysym_t sym); -void menu_destroy(struct menu *menu); #endif diff --git a/wmenu-run.c b/wmenu-run.c index fe79ece..cbc35a1 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -9,13 +10,19 @@ #include "xdg-activation-v1-client-protocol.h" static void read_items(struct menu *menu) { - char buf[sizeof menu->input]; - while (fgets(buf, sizeof buf, stdin)) { - char *p = strchr(buf, '\n'); - if (p) { - *p = '\0'; + char *path = getenv("PATH"); + for (char *p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) { + DIR *dir = opendir(p); + if (dir == NULL) { + continue; } - menu_add_item(menu, strdup(buf)); + for (struct dirent *ent = readdir(dir); ent != NULL; ent = readdir(dir)) { + if (ent->d_name[0] == '.') { + continue; + } + menu_add_item(menu, strdup(ent->d_name), true); + } + closedir(dir); } } diff --git a/wmenu.c b/wmenu.c index 7eae947..0a3d0b0 100644 --- a/wmenu.c +++ b/wmenu.c @@ -12,7 +12,7 @@ static void read_items(struct menu *menu) { if (p) { *p = '\0'; } - menu_add_item(menu, strdup(buf)); + menu_add_item(menu, strdup(buf), false); } } From e1816cc9a9ab7887dc2aa10b6244055546111049 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 3 May 2024 19:31:11 -0400 Subject: [PATCH 53/72] wmenu-run: Don't overwrite PATH --- wmenu-run.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wmenu-run.c b/wmenu-run.c index cbc35a1..228edc4 100644 --- a/wmenu-run.c +++ b/wmenu-run.c @@ -10,7 +10,7 @@ #include "xdg-activation-v1-client-protocol.h" static void read_items(struct menu *menu) { - char *path = getenv("PATH"); + char *path = strdup(getenv("PATH")); for (char *p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) { DIR *dir = opendir(p); if (dir == NULL) { @@ -24,6 +24,7 @@ static void read_items(struct menu *menu) { } closedir(dir); } + free(path); } struct executable { From 8f958478111933f8b1ce8405203026c2cc91fde8 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 3 May 2024 21:23:08 -0400 Subject: [PATCH 54/72] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 649e020..c4c7c63 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ See wmenu(1) To use wmenu with Sway, you can add the following to your configuration file: ``` -set $menu dmenu_path | wmenu-run +set $menu wmenu-run bindsym $mod+d exec $menu ``` From 81d46e39120bcb7c24cec2105e05f3524e8bd548 Mon Sep 17 00:00:00 2001 From: adnano Date: Fri, 3 May 2024 21:34:10 -0400 Subject: [PATCH 55/72] docs: Add wmenu-run --- docs/wmenu.1.scd | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index 407da9f..f489c84 100644 --- a/docs/wmenu.1.scd +++ b/docs/wmenu.1.scd @@ -1,4 +1,4 @@ -wmenu(1) +WMENU(1) # NAME @@ -15,13 +15,18 @@ wmenu - dynamic menu for Wayland [-M _color_] [-m _color_] \ [-S _color_] [-s _color_] +*wmenu-run* ... + # DESCRIPTION -wmenu is a dynamic menu for Wayland, which reads a list of newline-separated +*wmenu* is a dynamic menu for Wayland, which reads a list of newline-separated items from stdin. When the user selects an item and presses Return, their choice is printed to stdout and wmenu terminates. Entering text will narrow the items to those matching the tokens in the input. +*wmenu-run* is a special invocation of wmenu which lists programs in the user's +$PATH and runs the result. + # OPTIONS *-b* From c05ab7520b452ee3b8bd974a18511dc370cbeabe Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 4 May 2024 21:41:21 -0400 Subject: [PATCH 56/72] Remove wmenu -P flag This flag causes some issues with wmenu-run. It will be revisited in the next release. --- docs/wmenu.1.scd | 6 +----- menu.c | 5 +---- menu.h | 2 -- render.c | 18 ++---------------- wmenu.c | 4 +--- 5 files changed, 5 insertions(+), 30 deletions(-) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd index f489c84..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. All data from stdin will be ignored. - *-v* prints version information to stdout, then exits. diff --git a/menu.c b/menu.c index baa3061..3e8f9be 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, "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); diff --git a/menu.h b/menu.h index 724aa53..0a6b22c 100644 --- a/menu.h +++ b/menu.h @@ -30,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 diff --git a/render.c b/render.c index 3813af5..e33898d 100644 --- a/render.c +++ b/render.c @@ -79,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. 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 963a677631f00b9b259e101a5e3dad85da6ccbf2 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 4 May 2024 21:42:31 -0400 Subject: [PATCH 57/72] Version 0.1.8 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6a745aa..d2643fc 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wmenu', 'c', - version: '0.1.7', + version: '0.1.8', license: 'MIT', default_options: [ 'c_std=c11', From 15d7c7bcc29e66f174c4de2420d371a9737ac6e4 Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 4 May 2024 21:44:59 -0400 Subject: [PATCH 58/72] 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 59/72] 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 60/72] 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 61/72] 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 62/72] 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 63/72] 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 64/72] 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 65/72] 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 66/72] 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 67/72] 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 68/72] 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 69/72] 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 70/72] 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 71/72] 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 72/72] 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',