wmenu

wmenu fork with my settings
git clone https://git.awy.one/wmenu.git
Log | Files | Refs | README | LICENSE

menu.c (16331B)


      1 #define _POSIX_C_SOURCE 200809L
      2 #include <assert.h>
      3 #include <ctype.h>
      4 #include <poll.h>
      5 #include <stdbool.h>
      6 #include <signal.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <strings.h>
     11 #include <time.h>
     12 #include <unistd.h>
     13 #include <sys/mman.h>
     14 #include <sys/timerfd.h>
     15 #include <wayland-client.h>
     16 #include <wayland-client-protocol.h>
     17 #include <xkbcommon/xkbcommon.h>
     18 
     19 #include "menu.h"
     20 
     21 #include "pango.h"
     22 #include "render.h"
     23 #include "wayland.h"
     24 
     25 // Creates and returns a new menu.
     26 struct menu *menu_create(menu_callback callback) {
     27 	struct menu *menu = calloc(1, sizeof(struct menu));
     28 	menu->strncmp = strncmp;
     29 	menu->font = "monospace 12";
     30 	menu->normalbg = 0x282828ff;
     31 	menu->normalfg = 0xfbf1c7ff;
     32 	menu->promptbg = 0x83a598ff;
     33 	menu->promptfg = 0xfbf1c7ff;
     34 	menu->selectionbg = 0x83a598ff;
     35 	menu->selectionfg = 0xfbf1c7ff;
     36 	menu->callback = callback;
     37 	menu->test_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
     38 	menu->test_cairo = cairo_create(menu->test_surface);
     39 	return menu;
     40 }
     41 
     42 static void free_pages(struct menu *menu) {
     43 	struct page *next = menu->pages;
     44 	while (next) {
     45 		struct page *page = next;
     46 		next = page->next;
     47 		free(page);
     48 	}
     49 }
     50 
     51 static void free_items(struct menu *menu) {
     52 	for (size_t i = 0; i < menu->item_count; i++) {
     53 		struct item *item = &menu->items[i];
     54 		free(item->text);
     55 	}
     56 	free(menu->items);
     57 }
     58 
     59 // Destroys the menu, freeing memory associated with it.
     60 void menu_destroy(struct menu *menu) {
     61 	free_pages(menu);
     62 	free_items(menu);
     63 	cairo_destroy(menu->test_cairo);
     64 	cairo_surface_destroy(menu->test_surface);
     65 	free(menu);
     66 }
     67 
     68 static bool parse_color(const char *color, uint32_t *result) {
     69 	if (color[0] == '#') {
     70 		++color;
     71 	}
     72 	size_t len = strlen(color);
     73 	if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) {
     74 		return false;
     75 	}
     76 	char *ptr;
     77 	uint32_t parsed = (uint32_t)strtoul(color, &ptr, 16);
     78 	if (*ptr != '\0') {
     79 		return false;
     80 	}
     81 	*result = len == 6 ? ((parsed << 8) | 0xFF) : parsed;
     82 	return true;
     83 }
     84 
     85 // Parse menu options from command line arguments.
     86 void menu_getopts(struct menu *menu, int argc, char *argv[]) {
     87 	const char *usage =
     88 		"Usage: wmenu [-biPv] [-f font] [-l lines] [-o output] [-p prompt]\n"
     89 		"\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n";
     90 
     91 	int opt;
     92 	while ((opt = getopt(argc, argv, "bhiPvf:l:o:p:N:n:M:m:S:s:")) != -1) {
     93 		switch (opt) {
     94 		case 'b':
     95 			menu->bottom = true;
     96 			break;
     97 		case 'i':
     98 			menu->strncmp = strncasecmp;
     99 			break;
    100 		case 'P':
    101 			menu->passwd = true;
    102 			break;
    103 		case 'v':
    104 			puts("wmenu " VERSION);
    105 			exit(EXIT_SUCCESS);
    106 		case 'f':
    107 			menu->font = optarg;
    108 			break;
    109 		case 'l':
    110 			menu->lines = atoi(optarg);
    111 			break;
    112 		case 'o':
    113 			menu->output_name = optarg;
    114 			break;
    115 		case 'p':
    116 			menu->prompt = optarg;
    117 			break;
    118 		case 'N':
    119 			if (!parse_color(optarg, &menu->normalbg)) {
    120 				fprintf(stderr, "Invalid background color: %s", optarg);
    121 			}
    122 			break;
    123 		case 'n':
    124 			if (!parse_color(optarg, &menu->normalfg)) {
    125 				fprintf(stderr, "Invalid foreground color: %s", optarg);
    126 			}
    127 			break;
    128 		case 'M':
    129 			if (!parse_color(optarg, &menu->promptbg)) {
    130 				fprintf(stderr, "Invalid prompt background color: %s", optarg);
    131 			}
    132 			break;
    133 		case 'm':
    134 			if (!parse_color(optarg, &menu->promptfg)) {
    135 				fprintf(stderr, "Invalid prompt foreground color: %s", optarg);
    136 			}
    137 			break;
    138 		case 'S':
    139 			if (!parse_color(optarg, &menu->selectionbg)) {
    140 				fprintf(stderr, "Invalid selection background color: %s", optarg);
    141 			}
    142 			break;
    143 		case 's':
    144 			if (!parse_color(optarg, &menu->selectionfg)) {
    145 				fprintf(stderr, "Invalid selection foreground color: %s", optarg);
    146 			}
    147 			break;
    148 		default:
    149 			fprintf(stderr, "%s", usage);
    150 			exit(EXIT_FAILURE);
    151 		}
    152 	}
    153 
    154 	if (optind < argc) {
    155 		fprintf(stderr, "%s", usage);
    156 		exit(EXIT_FAILURE);
    157 	}
    158 
    159 	int height = get_font_height(menu->font);
    160 	menu->line_height = height + 2;
    161 	menu->height = menu->line_height;
    162 	if (menu->lines > 0) {
    163 		menu->height += menu->height * menu->lines;
    164 	}
    165 	menu->padding = height / 2;
    166 }
    167 
    168 // Add an item to the menu.
    169 void menu_add_item(struct menu *menu, char *text) {
    170 	if ((menu->item_count & (menu->item_count - 1)) == 0) {
    171 		size_t alloc_size = menu->item_count ? 2 * menu->item_count : 1;
    172 		void *new_array = realloc(menu->items, sizeof(struct item) * alloc_size);
    173 		if (!new_array) {
    174 			fprintf(stderr, "could not realloc %zu bytes", sizeof(struct item) * alloc_size);
    175 			exit(EXIT_FAILURE);
    176 		}
    177 		menu->items = new_array;
    178 	}
    179 
    180 	struct item *new = &menu->items[menu->item_count];
    181 	new->text = text;
    182 
    183 	menu->item_count++;
    184 }
    185 
    186 static int compare_items(const void *a, const void *b) {
    187 	const struct item *item_a = a;
    188 	const struct item *item_b = b;
    189 	return strcmp(item_a->text, item_b->text);
    190 }
    191 
    192 void menu_sort_and_deduplicate(struct menu *menu) {
    193 	size_t j = 1;
    194 	size_t i;
    195 
    196 	qsort(menu->items, menu->item_count, sizeof(*menu->items), compare_items);
    197 
    198 	for (i = 1; i < menu->item_count; i++) {
    199 		if (strcmp(menu->items[i].text, menu->items[j - 1].text) == 0) {
    200 			free(menu->items[i].text);
    201 		} else {
    202 			menu->items[j] = menu->items[i];
    203 			j++;
    204 		}
    205 	}
    206 	menu->item_count = j;
    207 }
    208 
    209 static void append_page(struct page *page, struct page **first, struct page **last) {
    210 	if (*last) {
    211 		(*last)->next = page;
    212 	} else {
    213 		*first = page;
    214 	}
    215 	page->prev = *last;
    216 	page->next = NULL;
    217 	*last = page;
    218 }
    219 
    220 static void page_items(struct menu *menu) {
    221 	// Free existing pages
    222 	while (menu->pages != NULL) {
    223 		struct page *page = menu->pages;
    224 		menu->pages = menu->pages->next;
    225 		free(page);
    226 	}
    227 
    228 	if (!menu->matches) {
    229 		return;
    230 	}
    231 
    232 	// Make new pages
    233 	if (menu->lines > 0) {
    234 		struct page *pages_end = NULL;
    235 		struct item *item = menu->matches;
    236 		while (item) {
    237 			struct page *page = calloc(1, sizeof(struct page));
    238 			page->first = item;
    239 
    240 			for (int i = 1; item && i <= menu->lines; i++) {
    241 				item->page = page;
    242 				page->last = item;
    243 				item = item->next_match;
    244 			}
    245 			append_page(page, &menu->pages, &pages_end);
    246 		}
    247 	} else {
    248 		// Calculate available space
    249 		int max_width = menu->width - menu->inputw - menu->promptw
    250 			- menu->left_arrow - menu->right_arrow;
    251 
    252 		struct page *pages_end = NULL;
    253 		struct item *item = menu->matches;
    254 		while (item) {
    255 			struct page *page = calloc(1, sizeof(struct page));
    256 			page->first = item;
    257 
    258 			int total_width = 0;
    259 			int items = 0;
    260 			while (item) {
    261 				total_width += item->width + 2 * menu->padding;
    262 				if (total_width > max_width && items > 0) {
    263 					break;
    264 				}
    265 				items++;
    266 
    267 				item->page = page;
    268 				page->last = item;
    269 				item = item->next_match;
    270 			}
    271 			append_page(page, &menu->pages, &pages_end);
    272 		}
    273 	}
    274 }
    275 
    276 static const char *fstrstr(struct menu *menu, const char *s, const char *sub) {
    277 	for (size_t len = strlen(sub); *s; s++) {
    278 		if (!menu->strncmp(s, sub, len)) {
    279 			return s;
    280 		}
    281 	}
    282 	return NULL;
    283 }
    284 
    285 static void append_match(struct item *item, struct item **first, struct item **last) {
    286 	if (*last) {
    287 		(*last)->next_match = item;
    288 	} else {
    289 		*first = item;
    290 	}
    291 	item->prev_match = *last;
    292 	item->next_match = NULL;
    293 	*last = item;
    294 }
    295 
    296 static void match_items(struct menu *menu) {
    297 	struct item *lexact = NULL, *exactend = NULL;
    298 	struct item *lprefix = NULL, *prefixend = NULL;
    299 	struct item *lsubstr  = NULL, *substrend = NULL;
    300 	char buf[sizeof menu->input], *tok;
    301 	char **tokv = NULL;
    302 	int i, tokc = 0;
    303 	size_t k;
    304 	size_t tok_len;
    305 	menu->matches = NULL;
    306 	menu->matches_end = NULL;
    307 	menu->sel = NULL;
    308 
    309 	size_t input_len = strlen(menu->input);
    310 
    311 	/* tokenize input by space for matching the tokens individually */
    312 	strcpy(buf, menu->input);
    313 	tok = strtok(buf, " ");
    314 	while (tok) {
    315 		tokv = realloc(tokv, (tokc + 1) * sizeof *tokv);
    316 		if (!tokv) {
    317 			fprintf(stderr, "could not realloc %zu bytes",
    318 					(tokc + 1) * sizeof *tokv);
    319 			exit(EXIT_FAILURE);
    320 		}
    321 		tokv[tokc] = tok;
    322 		tokc++;
    323 		tok = strtok(NULL, " ");
    324 	}
    325 	tok_len = tokc ? strlen(tokv[0]) : 0;
    326 
    327 	for (k = 0; k < menu->item_count; k++) {
    328 		struct item *item = &menu->items[k];
    329 		for (i = 0; i < tokc; i++) {
    330 			if (!fstrstr(menu, item->text, tokv[i])) {
    331 				/* token does not match */
    332 				break;
    333 			}
    334 		}
    335 		if (i != tokc) {
    336 			/* not all tokens match */
    337 			continue;
    338 		}
    339 		if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) {
    340 			append_match(item, &lexact, &exactend);
    341 		} else if (!menu->strncmp(tokv[0], item->text, tok_len)) {
    342 			append_match(item, &lprefix, &prefixend);
    343 		} else {
    344 			append_match(item, &lsubstr, &substrend);
    345 		}
    346 	}
    347 
    348 	free(tokv);
    349 
    350 	if (lexact) {
    351 		menu->matches = lexact;
    352 		menu->matches_end = exactend;
    353 	}
    354 	if (lprefix) {
    355 		if (menu->matches_end) {
    356 			menu->matches_end->next_match = lprefix;
    357 			lprefix->prev_match = menu->matches_end;
    358 		} else {
    359 			menu->matches = lprefix;
    360 		}
    361 		menu->matches_end = prefixend;
    362 	}
    363 	if (lsubstr) {
    364 		if (menu->matches_end) {
    365 			menu->matches_end->next_match = lsubstr;
    366 			lsubstr->prev_match = menu->matches_end;
    367 		} else {
    368 			menu->matches = lsubstr;
    369 		}
    370 		menu->matches_end = substrend;
    371 	}
    372 
    373 	page_items(menu);
    374 	if (menu->pages) {
    375 		menu->sel = menu->pages->first;
    376 	}
    377 }
    378 
    379 // Marks the menu as needing to be rendered again.
    380 void menu_invalidate(struct menu *menu) {
    381 	menu->rendered = false;
    382 }
    383 
    384 // Render menu items.
    385 void menu_render_items(struct menu *menu) {
    386 	calc_widths(menu);
    387 	match_items(menu);
    388 	render_menu(menu);
    389 }
    390 
    391 static void insert(struct menu *menu, const char *text, ssize_t len) {
    392 	if (strlen(menu->input) + len > sizeof menu->input - 1) {
    393 		return;
    394 	}
    395 	memmove(menu->input + menu->cursor + len, menu->input + menu->cursor,
    396 			sizeof menu->input - menu->cursor - MAX(len, 0));
    397 	if (len > 0 && text != NULL) {
    398 		memcpy(menu->input + menu->cursor, text, len);
    399 	}
    400 	menu->cursor += len;
    401 }
    402 
    403 // Add pasted text to the menu input.
    404 void menu_paste(struct menu *menu, const char *text, ssize_t len) {
    405 	insert(menu, text, len);
    406 }
    407 
    408 static size_t nextrune(struct menu *menu, int incr) {
    409 	size_t n, len;
    410 
    411 	len = strlen(menu->input);
    412 	for(n = menu->cursor + incr; n < len && (menu->input[n] & 0xc0) == 0x80; n += incr);
    413 	return n;
    414 }
    415 
    416 // Move the cursor to the beginning or end of the word, skipping over any preceding whitespace.
    417 static void movewordedge(struct menu *menu, int dir) {
    418 	if (dir < 0) {
    419 		// Move to beginning of word
    420 		while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') {
    421 			menu->cursor = nextrune(menu, -1);
    422 		}
    423 		while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') {
    424 			menu->cursor = nextrune(menu, -1);
    425 		}
    426 	} else {
    427 		// Move to end of word
    428 		size_t len = strlen(menu->input);
    429 		while (menu->cursor < len && menu->input[menu->cursor] == ' ') {
    430 			menu->cursor = nextrune(menu, +1);
    431 		}
    432 		while (menu->cursor < len && menu->input[menu->cursor] != ' ') {
    433 			menu->cursor = nextrune(menu, +1);
    434 		}
    435 	}
    436 }
    437 
    438 // Handle a keypress.
    439 void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
    440 		xkb_keysym_t sym) {
    441 	if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) {
    442 		return;
    443 	}
    444 
    445 	struct xkb_state *state = context_get_xkb_state(menu->context);
    446 	bool ctrl = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
    447 			XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
    448 	bool meta = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
    449 			XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
    450 	bool shift = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT,
    451 			XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
    452 
    453 	size_t len = strlen(menu->input);
    454 
    455 	if (ctrl) {
    456 		// Emacs-style line editing bindings
    457 		switch (sym) {
    458 		case XKB_KEY_a:
    459 			sym = XKB_KEY_Home;
    460 			break;
    461 		case XKB_KEY_b:
    462 			sym = XKB_KEY_Left;
    463 			break;
    464 		case XKB_KEY_c:
    465 			sym = XKB_KEY_Escape;
    466 			break;
    467 		case XKB_KEY_d:
    468 			sym = XKB_KEY_Delete;
    469 			break;
    470 		case XKB_KEY_e:
    471 			sym = XKB_KEY_End;
    472 			break;
    473 		case XKB_KEY_f:
    474 			sym = XKB_KEY_Right;
    475 			break;
    476 		case XKB_KEY_g:
    477 			sym = XKB_KEY_Escape;
    478 			break;
    479 		case XKB_KEY_bracketleft:
    480 			sym = XKB_KEY_Escape;
    481 			break;
    482 		case XKB_KEY_h:
    483 			sym = XKB_KEY_BackSpace;
    484 			break;
    485 		case XKB_KEY_i:
    486 			sym = XKB_KEY_Tab;
    487 			break;
    488 		case XKB_KEY_j:
    489 		case XKB_KEY_J:
    490 		case XKB_KEY_m:
    491 		case XKB_KEY_M:
    492 			sym = XKB_KEY_Return;
    493 			ctrl = false;
    494 			break;
    495 		case XKB_KEY_n:
    496 			sym = XKB_KEY_Down;
    497 			break;
    498 		case XKB_KEY_p:
    499 			sym = XKB_KEY_Up;
    500 			break;
    501 
    502 		case XKB_KEY_k:
    503 			// Delete right
    504 			menu->input[menu->cursor] = '\0';
    505 			match_items(menu);
    506 			menu_invalidate(menu);
    507 			return;
    508 		case XKB_KEY_u:
    509 			// Delete left
    510 			insert(menu, NULL, 0 - menu->cursor);
    511 			match_items(menu);
    512 			menu_invalidate(menu);
    513 			return;
    514 		case XKB_KEY_w:
    515 			// Delete word
    516 			while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') {
    517 				insert(menu, NULL, nextrune(menu, -1) - menu->cursor);
    518 			}
    519 			while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') {
    520 				insert(menu, NULL, nextrune(menu, -1) - menu->cursor);
    521 			}
    522 			match_items(menu);
    523 			menu_invalidate(menu);
    524 			return;
    525 		case XKB_KEY_Y:
    526 			// Paste clipboard
    527 			if (!context_paste(menu->context)) {
    528 				return;
    529 			}
    530 			match_items(menu);
    531 			menu_invalidate(menu);
    532 			return;
    533 		case XKB_KEY_Left:
    534 		case XKB_KEY_KP_Left:
    535 			movewordedge(menu, -1);
    536 			menu_invalidate(menu);
    537 			return;
    538 		case XKB_KEY_Right:
    539 		case XKB_KEY_KP_Right:
    540 			movewordedge(menu, +1);
    541 			menu_invalidate(menu);
    542 			return;
    543 
    544 		case XKB_KEY_Return:
    545 		case XKB_KEY_KP_Enter:
    546 			break;
    547 		default:
    548 			return;
    549 		}
    550 	} else if (meta) {
    551 		// Emacs-style line editing bindings
    552 		switch (sym) {
    553 		case XKB_KEY_b:
    554 			movewordedge(menu, -1);
    555 			menu_invalidate(menu);
    556 			return;
    557 		case XKB_KEY_f:
    558 			movewordedge(menu, +1);
    559 			menu_invalidate(menu);
    560 			return;
    561 		case XKB_KEY_g:
    562 			sym = XKB_KEY_Home;
    563 			break;
    564 		case XKB_KEY_G:
    565 			sym = XKB_KEY_End;
    566 			break;
    567 		case XKB_KEY_h:
    568 			sym = XKB_KEY_Up;
    569 			break;
    570 		case XKB_KEY_j:
    571 			sym = XKB_KEY_Next;
    572 			break;
    573 		case XKB_KEY_k:
    574 			sym = XKB_KEY_Prior;
    575 			break;
    576 		case XKB_KEY_l:
    577 			sym = XKB_KEY_Down;
    578 			break;
    579 		default:
    580 			return;
    581 		}
    582 	}
    583 
    584 	char buf[8];
    585 	switch (sym) {
    586 	case XKB_KEY_Return:
    587 	case XKB_KEY_KP_Enter:
    588 		if (shift) {
    589 			menu->callback(menu, menu->input, true);
    590 		} else {
    591 			char *text = menu->sel ? menu->sel->text : menu->input;
    592 			menu->callback(menu, text, !ctrl);
    593 		}
    594 		break;
    595 	case XKB_KEY_Left:
    596 	case XKB_KEY_KP_Left:
    597 	case XKB_KEY_Up:
    598 	case XKB_KEY_KP_Up:
    599 		if (menu->sel && menu->sel->prev_match) {
    600 			menu->sel = menu->sel->prev_match;
    601 			menu_invalidate(menu);
    602 		} else if (menu->cursor > 0) {
    603 			menu->cursor = nextrune(menu, -1);
    604 			menu_invalidate(menu);
    605 		}
    606 		break;
    607 	case XKB_KEY_Right:
    608 	case XKB_KEY_KP_Right:
    609 	case XKB_KEY_Down:
    610 	case XKB_KEY_KP_Down:
    611 		if (menu->cursor < len) {
    612 			menu->cursor = nextrune(menu, +1);
    613 			menu_invalidate(menu);
    614 		} else if (menu->sel && menu->sel->next_match) {
    615 			menu->sel = menu->sel->next_match;
    616 			menu_invalidate(menu);
    617 		}
    618 		break;
    619 	case XKB_KEY_Prior:
    620 	case XKB_KEY_KP_Prior:
    621 		if (menu->sel && menu->sel->page->prev) {
    622 			menu->sel = menu->sel->page->prev->first;
    623 			menu_invalidate(menu);
    624 		}
    625 		break;
    626 	case XKB_KEY_Next:
    627 	case XKB_KEY_KP_Next:
    628 		if (menu->sel && menu->sel->page->next) {
    629 			menu->sel = menu->sel->page->next->first;
    630 			menu_invalidate(menu);
    631 		}
    632 		break;
    633 	case XKB_KEY_Home:
    634 	case XKB_KEY_KP_Home:
    635 		if (menu->sel == menu->matches) {
    636 			menu->cursor = 0;
    637 			menu_invalidate(menu);
    638 		} else {
    639 			menu->sel = menu->matches;
    640 			menu_invalidate(menu);
    641 		}
    642 		break;
    643 	case XKB_KEY_End:
    644 	case XKB_KEY_KP_End:
    645 		if (menu->cursor < len) {
    646 			menu->cursor = len;
    647 			menu_invalidate(menu);
    648 		} else {
    649 			menu->sel = menu->matches_end;
    650 			menu_invalidate(menu);
    651 		}
    652 		break;
    653 	case XKB_KEY_BackSpace:
    654 		if (menu->cursor > 0) {
    655 			insert(menu, NULL, nextrune(menu, -1) - menu->cursor);
    656 			match_items(menu);
    657 			menu_invalidate(menu);
    658 		}
    659 		break;
    660 	case XKB_KEY_Delete:
    661 	case XKB_KEY_KP_Delete:
    662 		if (menu->cursor == len) {
    663 			return;
    664 		}
    665 		menu->cursor = nextrune(menu, +1);
    666 		insert(menu, NULL, nextrune(menu, -1) - menu->cursor);
    667 		match_items(menu);
    668 		menu_invalidate(menu);
    669 		break;
    670 	case XKB_KEY_Tab:
    671 		if (!menu->sel) {
    672 			return;
    673 		}
    674 		menu->cursor = strnlen(menu->sel->text, sizeof menu->input - 1);
    675 		memcpy(menu->input, menu->sel->text, menu->cursor);
    676 		menu->input[menu->cursor] = '\0';
    677 		match_items(menu);
    678 		menu_invalidate(menu);
    679 		break;
    680 	case XKB_KEY_Escape:
    681 		menu->exit = true;
    682 		menu->failure = true;
    683 		break;
    684 	default:
    685 		if (xkb_keysym_to_utf8(sym, buf, 8)) {
    686 			insert(menu, buf, strnlen(buf, 8));
    687 			match_items(menu);
    688 			menu_invalidate(menu);
    689 		}
    690 	}
    691 }