Compare commits

...

31 commits
0.1.7 ... main

Author SHA1 Message Date
00478c32cb chore: build a shared library 2025-01-24 21:29:47 -06:00
adnano
e2542d34ed Render frame on surface enter
This ensures that the menu is rendered with the correct scale.

Fixes #14
2024-12-16 10:56:51 -05:00
M Stoeckl
3ad4b5ca3f Simplify render_menu 2024-11-08 15:28:11 -05:00
adnano
48ec172b4b README: Update meson instructions 2024-11-01 19:35:39 -04:00
M Stoeckl
0947765fc9 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.
2024-11-01 23:33:53 +00:00
M Stoeckl
260eaba88e 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.
2024-10-31 09:30:09 -04:00
adnano
12b8f83be4 Display over fullscreen applications 2024-08-03 18:26:59 -04:00
adnano
8b23811263 Version 0.1.9 2024-06-09 20:33:37 -04:00
adnano
7d717b3696 Streamline menu callbacks 2024-06-09 20:30:58 -04:00
NAHTAIV3L
a0df7959f9 Make wmenu-run behave like dmenu_run 2024-06-09 19:02:32 -04:00
adnano
0fa9c35949 Update README.md 2024-05-25 19:10:07 -04:00
adnano
30abca4f30 Don't ignore stdin in password mode
This makes password mode work for wmenu and wmenu-run without special
cases.
2024-05-05 10:13:01 -04:00
adnano
15d7c7bcc2 Revert "Remove wmenu -P flag"
This reverts commit c05ab7520b.
2024-05-04 21:44:59 -04:00
adnano
963a677631 Version 0.1.8 2024-05-04 21:42:31 -04:00
adnano
c05ab7520b Remove wmenu -P flag
This flag causes some issues with wmenu-run. It will be revisited in the
next release.
2024-05-04 21:41:21 -04:00
adnano
81d46e3912 docs: Add wmenu-run 2024-05-03 21:34:10 -04:00
adnano
8f95847811 Update README.md 2024-05-03 21:23:08 -04:00
adnano
e1816cc9a9 wmenu-run: Don't overwrite PATH 2024-05-03 19:31:11 -04:00
adnano
8f19d6a8d2 wmenu-run: Populate items from PATH 2024-05-03 19:10:28 -04:00
adnano
92d3b294ae Update README.md 2024-05-02 21:41:26 -04:00
adnano
477c0419b4 Remove wmenu_run script 2024-05-02 21:40:46 -04:00
adnano
41e8599392 Add wmenu-run executable 2024-05-02 21:39:54 -04:00
adnano
1f221a73cf Fix destruction of pool buffers 2024-05-02 18:45:49 -04:00
adnano
6284eea24b Separate menu state from Wayland state 2024-05-02 17:03:07 -04:00
adnano
6a39269d2e Drop wmenu -x option 2024-05-02 14:44:09 -04:00
sewn
e4c4627eeb make menu height accurate to dwm, dmenu, and dwl's bar patch 2024-04-14 17:22:09 -04:00
adnano
cf6f5b9d06 Support xdg_activation_v1 protocol 2024-04-07 08:51:57 -04:00
adnano
41b2e8b1e1 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.
2024-03-25 08:20:36 -04:00
sewn
ac25b07338 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 <sinan@firemail.cc>
2024-03-17 07:49:14 -04:00
sewn
9e9284666c port dmenu password patch 2024-03-17 07:33:55 -04:00
adnano
6ad7a303ef 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.
2024-03-17 07:01:23 -04:00
12 changed files with 873 additions and 603 deletions

View file

@ -15,7 +15,7 @@ Dependencies:
- scdoc (optional)
```
$ meson build
$ meson setup build
$ ninja -C build
# ninja -C build install
```
@ -27,17 +27,6 @@ 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
```
## 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.

View file

@ -1,4 +1,4 @@
wmenu(1)
WMENU(1)
# NAME
@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland
# SYNOPSIS
*wmenu* [-biv] \
*wmenu* [-biPv] \
[-f _font_] \
[-l _lines_] \
[-o _output_] \
@ -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*
@ -30,6 +35,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.
*-v*
prints version information to stdout, then exits.

325
main.c
View file

@ -1,325 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdbool.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "menu.h"
#include "render.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.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 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);
}
}
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, &registry_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);
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);
}
}
bool failure = menu->failure;
menu_destroy(menu);
if (failure) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

275
menu.c
View file

@ -19,12 +19,11 @@
#include "menu.h"
#include "pango.h"
#include "pool-buffer.h"
#include "render.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#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";
@ -34,39 +33,36 @@ struct menu *menu_create() {
menu->promptfg = 0xeeeeeeff;
menu->selectionbg = 0x005577ff;
menu->selectionfg = 0xeeeeeeff;
menu->callback = callback;
menu->test_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
menu->test_cairo = cairo_create(menu->test_surface);
return menu;
}
// 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;
static void free_pages(struct menu *menu) {
struct page *next = menu->pages;
while (next) {
struct page *page = next;
next = page->next;
free(page);
}
}
// Sets the current keyboard.
void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard) {
menu->keyboard = keyboard;
static void free_items(struct menu *menu) {
for (size_t i = 0; i < menu->item_count; i++) {
struct item *item = &menu->items[i];
free(item->text);
}
free(menu->items);
}
// 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;
// Destroys the menu, freeing memory associated with it.
void menu_destroy(struct menu *menu) {
free_pages(menu);
free_items(menu);
cairo_destroy(menu->test_cairo);
cairo_surface_destroy(menu->test_surface);
free(menu);
}
static bool parse_color(const char *color, uint32_t *result) {
@ -89,11 +85,11 @@ static bool parse_color(const char *color, uint32_t *result) {
// Parse menu options from command line arguments.
void menu_getopts(struct menu *menu, int argc, char *argv[]) {
const char *usage =
"Usage: wmenu [-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 +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);
@ -158,7 +157,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;
@ -166,6 +165,47 @@ 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) {
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;
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++;
}
}
menu->item_count = j;
}
static void append_page(struct page *page, struct page **first, struct page **last) {
if (*last) {
(*last)->next = page;
@ -216,11 +256,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;
@ -240,7 +282,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 {
@ -258,6 +300,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;
@ -281,8 +324,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 */
@ -294,11 +337,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);
}
}
@ -333,40 +376,28 @@ 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];
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;
}
// Render menu items.
void menu_render_items(struct menu *menu) {
calc_widths(menu);
match_items(menu);
render_menu(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) {
@ -406,14 +437,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);
@ -490,32 +519,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;
@ -575,16 +581,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;
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:
@ -684,70 +684,3 @@ 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);
}

72
menu.h
View file

@ -1,15 +1,19 @@
#ifndef WMENU_MENU_H
#define WMENU_MENU_H
#include <cairo/cairo.h>
#include <stdbool.h>
#include <sys/types.h>
#include <xkbcommon/xkbcommon.h>
#include <wayland-client.h>
#include "pool-buffer.h"
struct menu;
typedef void (*menu_callback)(struct menu *menu, char *text, bool exit);
// A menu item.
struct item {
char *text;
int width;
struct item *next; // traverses all items
struct item *prev_match; // previous matching item
struct item *next_match; // next matching item
struct page *page; // the page holding this item
@ -23,36 +27,14 @@ 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
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
@ -68,24 +50,11 @@ 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 wl_context *context;
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 pool_buffer buffers[2];
struct pool_buffer *current;
// 1x1 surface used estimate text sizes with pango
cairo_surface_t *test_surface;
cairo_t *test_cairo;
int width;
int height;
@ -99,25 +68,26 @@ struct menu {
char input[BUFSIZ];
size_t cursor;
struct item *items; // list of all items
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
struct page *pages; // list of pages
menu_callback callback;
bool exit;
bool failure;
};
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);
struct menu *menu_create(menu_callback callback);
void menu_destroy(struct menu *menu);
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_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,
xkb_keysym_t sym);
void menu_destroy(struct menu *menu);
#endif

View file

@ -1,7 +1,7 @@
project(
'wmenu',
'c',
version: '0.1.7',
version: '0.1.9',
license: 'MIT',
default_options: [
'c_std=c11',
@ -33,14 +33,60 @@ rt = cc.find_library('rt')
subdir('protocols')
subdir('docs')
executable(
shared_library(
'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,
],
)
executable(
'wmenu',
files(
'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,

View file

@ -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'],
]

View file

@ -1,13 +1,25 @@
#define _POSIX_C_SOURCE 200809L
#include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
#include "render.h"
#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;
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) {
@ -21,7 +33,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;
@ -72,8 +85,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.
@ -160,41 +187,24 @@ static void render_to_cairo(struct menu *menu, cairo_t *cairo) {
// 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);
struct wl_context *context = menu->context;
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;
int scale = context_get_scale(context);
struct pool_buffer *buffer = context_get_next_buffer(context, scale);
if (!buffer) {
return;
}
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);
cairo_t *shm = buffer->cairo;
cairo_set_antialias(shm, CAIRO_ANTIALIAS_BEST);
cairo_font_options_t *fo = cairo_font_options_create();
cairo_set_font_options(shm, fo);
cairo_font_options_destroy(fo);
render_to_cairo(menu, shm);
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);
cairo_surface_destroy(recorder);
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);
}

505
wayland.c Normal file
View file

@ -0,0 +1,505 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdbool.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "menu.h"
#include "pool-buffer.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;
struct pool_buffer buffers[2];
struct pool_buffer *current;
};
// Returns the current output_scale.
int context_get_scale(struct wl_context *context) {
return context->output ? context->output->scale : 1;
}
// 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.
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;
}
// 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) {
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);
menu_render_items(context->menu);
}
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, &registry_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_OVERLAY,
"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);
menu_render_items(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);
}
}
context_destroy(context);
menu->context = NULL;
if (menu->failure) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

19
wayland.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef WMENU_WAYLAND_H
#define WMENU_WAYLAND_H
#include "menu.h"
#include <wayland-client-protocol.h>
struct wl_context;
int menu_run(struct menu *menu);
int context_get_scale(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);
struct xdg_activation_v1 *context_get_xdg_activation(struct wl_context *context);
bool context_paste(struct wl_context *context);
#endif

78
wmenu-run.c Normal file
View file

@ -0,0 +1,78 @@
#define _POSIX_C_SOURCE 200809L
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "menu.h"
#include "wayland.h"
#include "xdg-activation-v1-client-protocol.h"
static void read_items(struct menu *menu) {
char *path = strdup(getenv("PATH"));
for (char *p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
DIR *dir = opendir(p);
if (dir == NULL) {
continue;
}
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));
}
closedir(dir);
}
menu_sort_and_deduplicate(menu);
free(path);
}
struct command {
struct menu *menu;
char *text;
bool exit;
};
static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token,
const char *token) {
struct command *cmd = data;
xdg_activation_token_v1_destroy(activation_token);
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_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, cmd);
xdg_activation_token_v1_commit(activation_token);
}
int main(int argc, char *argv[]) {
struct menu *menu = menu_create(exec_item);
menu_getopts(menu, argc, argv);
read_items(menu);
int status = menu_run(menu);
menu_destroy(menu);
return status;
}

35
wmenu.c Normal file
View file

@ -0,0 +1,35 @@
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include "menu.h"
#include "wayland.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));
}
}
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(print_item);
menu_getopts(menu, argc, argv);
read_items(menu);
int status = menu_run(menu);
menu_destroy(menu);
return status;
}