Compare commits

..

No commits in common. "main" and "0.1.7" have entirely different histories.
main ... 0.1.7

12 changed files with 602 additions and 872 deletions

View file

@ -15,7 +15,7 @@ Dependencies:
- scdoc (optional)
```
$ meson setup build
$ meson build
$ ninja -C build
# ninja -C build install
```
@ -27,6 +27,17 @@ 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
```
## 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* [-biPv] \
*wmenu* [-biv] \
[-f _font_] \
[-l _lines_] \
[-o _output_] \
@ -15,18 +15,13 @@ 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*
@ -35,10 +30,6 @@ $PATH and runs the result.
*-i*
wmenu matches menu items case insensitively.
*-P*
wmenu will not directly display the keyboard input, but instead replace it
with asterisks.
*-v*
prints version information to stdout, then exits.

325
main.c Normal file
View file

@ -0,0 +1,325 @@
#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;
}

277
menu.c
View file

@ -19,11 +19,12 @@
#include "menu.h"
#include "pango.h"
#include "pool-buffer.h"
#include "render.h"
#include "wayland.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
// Creates and returns a new menu.
struct menu *menu_create(menu_callback callback) {
struct menu *menu_create() {
struct menu *menu = calloc(1, sizeof(struct menu));
menu->strncmp = strncmp;
menu->font = "monospace 10";
@ -33,36 +34,39 @@ struct menu *menu_create(menu_callback callback) {
menu->promptfg = 0xeeeeeeff;
menu->selectionbg = 0x005577ff;
menu->selectionfg = 0xeeeeeeff;
menu->callback = callback;
menu->test_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
menu->test_cairo = cairo_create(menu->test_surface);
return menu;
}
static void free_pages(struct menu *menu) {
struct page *next = menu->pages;
while (next) {
struct page *page = next;
next = page->next;
free(page);
}
// 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_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);
// Sets the current keyboard.
void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard) {
menu->keyboard = keyboard;
}
// 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);
// 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) {
@ -85,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 [-biPv] [-f font] [-l lines] [-o output] [-p prompt]\n"
"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, "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 +101,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);
@ -157,7 +158,7 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) {
}
int height = get_font_height(menu->font);
menu->line_height = height + 2;
menu->line_height = height + 3;
menu->height = menu->line_height;
if (menu->lines > 0) {
menu->height += menu->height * menu->lines;
@ -165,47 +166,6 @@ 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;
@ -256,13 +216,11 @@ 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 && items > 0) {
if (total_width > max_width) {
break;
}
items++;
item->page = page;
page->last = item;
@ -282,7 +240,7 @@ static const char *fstrstr(struct menu *menu, const char *s, const char *sub) {
return NULL;
}
static void append_match(struct item *item, struct item **first, struct item **last) {
static void append_item(struct item *item, struct item **first, struct item **last) {
if (*last) {
(*last)->next_match = item;
} else {
@ -300,7 +258,6 @@ static void match_items(struct menu *menu) {
char buf[sizeof menu->input], *tok;
char **tokv = NULL;
int i, tokc = 0;
size_t k;
size_t tok_len;
menu->matches = NULL;
menu->matches_end = NULL;
@ -324,8 +281,8 @@ static void match_items(struct menu *menu) {
}
tok_len = tokc ? strlen(tokv[0]) : 0;
for (k = 0; k < menu->item_count; k++) {
struct item *item = &menu->items[k];
struct item *item;
for (item = menu->items; item; item = item->next) {
for (i = 0; i < tokc; i++) {
if (!fstrstr(menu, item->text, tokv[i])) {
/* token does not match */
@ -337,11 +294,11 @@ static void match_items(struct menu *menu) {
continue;
}
if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) {
append_match(item, &lexact, &exactend);
append_item(item, &lexact, &exactend);
} else if (!menu->strncmp(tokv[0], item->text, tok_len)) {
append_match(item, &lprefix, &prefixend);
append_item(item, &lprefix, &prefixend);
} else {
append_match(item, &lsubstr, &substrend);
append_item(item, &lsubstr, &substrend);
}
}
@ -376,28 +333,40 @@ static void match_items(struct menu *menu) {
}
}
// Render menu items.
void menu_render_items(struct menu *menu) {
calc_widths(menu);
match_items(menu);
render_menu(menu);
}
// Read menu items from standard input.
void read_menu_items(struct menu *menu) {
char buf[sizeof menu->input];
static void insert(struct menu *menu, const char *text, ssize_t len) {
if (strlen(menu->input) + len > sizeof menu->input - 1) {
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;
}
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);
item->text = strdup(buf);
*next = item;
next = &item->next;
}
menu->cursor += len;
calc_widths(menu);
match_items(menu);
}
// Add pasted text to the menu input.
void menu_paste(struct menu *menu, const char *text, ssize_t len) {
insert(menu, text, len);
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) {
@ -437,12 +406,14 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
return;
}
struct xkb_state *state = context_get_xkb_state(menu->context);
bool ctrl = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
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(state, XKB_MOD_NAME_ALT,
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(state, XKB_MOD_NAME_SHIFT,
bool shift = xkb_state_mod_name_is_active(menu->keyboard->state,
XKB_MOD_NAME_SHIFT,
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
size_t len = strlen(menu->input);
@ -519,9 +490,32 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
return;
case XKB_KEY_Y:
// Paste clipboard
if (!context_paste(menu->context)) {
if (!menu->data_offer) {
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;
@ -581,10 +575,16 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
case XKB_KEY_Return:
case XKB_KEY_KP_Enter:
if (shift) {
menu->callback(menu, menu->input, true);
puts(menu->input);
fflush(stdout);
menu->exit = true;
} else {
char *text = menu->sel ? menu->sel->text : menu->input;
menu->callback(menu, text, !ctrl);
puts(text);
fflush(stdout);
if (!ctrl) {
menu->exit = true;
}
}
break;
case XKB_KEY_Left:
@ -684,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);
}

72
menu.h
View file

@ -1,19 +1,15 @@
#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>
struct menu;
typedef void (*menu_callback)(struct menu *menu, char *text, bool exit);
#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
@ -27,14 +23,36 @@ 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
@ -50,11 +68,24 @@ struct menu {
// Selection colors
uint32_t selectionbg, selectionfg;
struct wl_context *context;
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;
// 1x1 surface used estimate text sizes with pango
cairo_surface_t *test_surface;
cairo_t *test_cairo;
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;
int width;
int height;
@ -68,26 +99,25 @@ struct menu {
char input[BUFSIZ];
size_t cursor;
struct item *items; // array of all items
size_t item_count;
struct item *items; // list of all items
struct item *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(menu_callback callback);
void menu_destroy(struct menu *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 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 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

View file

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

View file

@ -12,7 +12,6 @@ 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,25 +1,13 @@
#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) {
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;
cairo_t *cairo = menu->current->cairo;
// Calculate prompt width
if (menu->prompt) {
@ -33,8 +21,7 @@ void calc_widths(struct menu *menu) {
menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding;
// Calculate item widths and input area width
for (size_t i = 0; i < menu->item_count; i++) {
struct item *item = &menu->items[i];
for (struct item *item = menu->items; item; item = item->next) {
item->width = text_width(cairo, menu->font, item->text);
if (item->width > menu->inputw) {
menu->inputw = item->width;
@ -85,22 +72,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.
@ -187,24 +160,41 @@ 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);
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);
int scale = context_get_scale(context);
struct pool_buffer *buffer = context_get_next_buffer(context, scale);
if (!buffer) {
return;
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 = buffer->cairo;
cairo_set_antialias(shm, CAIRO_ANTIALIAS_BEST);
cairo_font_options_t *fo = cairo_font_options_create();
cairo_set_font_options(shm, fo);
cairo_font_options_destroy(fo);
render_to_cairo(menu, shm);
cairo_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);
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);
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);
}

505
wayland.c
View file

@ -1,505 +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 "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;
}

View file

@ -1,19 +0,0 @@
#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

View file

@ -1,78 +0,0 @@
#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
View file

@ -1,35 +0,0 @@
#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;
}