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) - scdoc (optional)
``` ```
$ meson setup build $ meson build
$ ninja -C build $ ninja -C build
# ninja -C build install # 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: 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 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 # NAME
@ -6,7 +6,7 @@ wmenu - dynamic menu for Wayland
# SYNOPSIS # SYNOPSIS
*wmenu* [-biPv] \ *wmenu* [-biv] \
[-f _font_] \ [-f _font_] \
[-l _lines_] \ [-l _lines_] \
[-o _output_] \ [-o _output_] \
@ -15,18 +15,13 @@ wmenu - dynamic menu for Wayland
[-M _color_] [-m _color_] \ [-M _color_] [-m _color_] \
[-S _color_] [-s _color_] [-S _color_] [-s _color_]
*wmenu-run* ...
# DESCRIPTION # 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 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 is printed to stdout and wmenu terminates. Entering text will narrow the items
to those matching the tokens in the input. 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 # OPTIONS
*-b* *-b*
@ -35,10 +30,6 @@ $PATH and runs the result.
*-i* *-i*
wmenu matches menu items case insensitively. wmenu matches menu items case insensitively.
*-P*
wmenu will not directly display the keyboard input, but instead replace it
with asterisks.
*-v* *-v*
prints version information to stdout, then exits. 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;
}

279
menu.c
View file

@ -19,11 +19,12 @@
#include "menu.h" #include "menu.h"
#include "pango.h" #include "pango.h"
#include "pool-buffer.h"
#include "render.h" #include "render.h"
#include "wayland.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
// Creates and returns a new menu. // 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)); struct menu *menu = calloc(1, sizeof(struct menu));
menu->strncmp = strncmp; menu->strncmp = strncmp;
menu->font = "monospace 10"; menu->font = "monospace 10";
@ -33,36 +34,39 @@ struct menu *menu_create(menu_callback callback) {
menu->promptfg = 0xeeeeeeff; menu->promptfg = 0xeeeeeeff;
menu->selectionbg = 0x005577ff; menu->selectionbg = 0x005577ff;
menu->selectionfg = 0xeeeeeeff; 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; return menu;
} }
static void free_pages(struct menu *menu) { // Creates and returns a new keyboard.
struct page *next = menu->pages; struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard) {
while (next) { struct keyboard *keyboard = calloc(1, sizeof(struct keyboard));
struct page *page = next; keyboard->menu = menu;
next = page->next; keyboard->keyboard = wl_keyboard;
free(page); 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) { // Sets the current keyboard.
for (size_t i = 0; i < menu->item_count; i++) { void menu_set_keyboard(struct menu *menu, struct keyboard *keyboard) {
struct item *item = &menu->items[i]; menu->keyboard = keyboard;
free(item->text);
}
free(menu->items);
} }
// Destroys the menu, freeing memory associated with it. // Creates and returns a new output.
void menu_destroy(struct menu *menu) { struct output *output_create(struct menu *menu, struct wl_output *wl_output) {
free_pages(menu); struct output *output = calloc(1, sizeof(struct output));
free_items(menu); output->menu = menu;
cairo_destroy(menu->test_cairo); output->output = wl_output;
cairo_surface_destroy(menu->test_surface); output->scale = 1;
free(menu); 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) { 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. // Parse menu options from command line arguments.
void menu_getopts(struct menu *menu, int argc, char *argv[]) { void menu_getopts(struct menu *menu, int argc, char *argv[]) {
const char *usage = 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"; "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n";
int opt; 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) { switch (opt) {
case 'b': case 'b':
menu->bottom = true; menu->bottom = true;
@ -97,9 +101,6 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) {
case 'i': case 'i':
menu->strncmp = strncasecmp; menu->strncmp = strncasecmp;
break; break;
case 'P':
menu->passwd = true;
break;
case 'v': case 'v':
puts("wmenu " VERSION); puts("wmenu " VERSION);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -157,7 +158,7 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) {
} }
int height = get_font_height(menu->font); int height = get_font_height(menu->font);
menu->line_height = height + 2; menu->line_height = height + 3;
menu->height = menu->line_height; menu->height = menu->line_height;
if (menu->lines > 0) { if (menu->lines > 0) {
menu->height += menu->height * menu->lines; menu->height += menu->height * menu->lines;
@ -165,47 +166,6 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) {
menu->padding = height / 2; 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) { static void append_page(struct page *page, struct page **first, struct page **last) {
if (*last) { if (*last) {
(*last)->next = page; (*last)->next = page;
@ -256,13 +216,11 @@ static void page_items(struct menu *menu) {
page->first = item; page->first = item;
int total_width = 0; int total_width = 0;
int items = 0;
while (item) { while (item) {
total_width += item->width + 2 * menu->padding; total_width += item->width + 2 * menu->padding;
if (total_width > max_width && items > 0) { if (total_width > max_width) {
break; break;
} }
items++;
item->page = page; item->page = page;
page->last = item; page->last = item;
@ -282,7 +240,7 @@ static const char *fstrstr(struct menu *menu, const char *s, const char *sub) {
return NULL; 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) { if (*last) {
(*last)->next_match = item; (*last)->next_match = item;
} else { } else {
@ -300,7 +258,6 @@ static void match_items(struct menu *menu) {
char buf[sizeof menu->input], *tok; char buf[sizeof menu->input], *tok;
char **tokv = NULL; char **tokv = NULL;
int i, tokc = 0; int i, tokc = 0;
size_t k;
size_t tok_len; size_t tok_len;
menu->matches = NULL; menu->matches = NULL;
menu->matches_end = NULL; menu->matches_end = NULL;
@ -324,8 +281,8 @@ static void match_items(struct menu *menu) {
} }
tok_len = tokc ? strlen(tokv[0]) : 0; tok_len = tokc ? strlen(tokv[0]) : 0;
for (k = 0; k < menu->item_count; k++) { struct item *item;
struct item *item = &menu->items[k]; for (item = menu->items; item; item = item->next) {
for (i = 0; i < tokc; i++) { for (i = 0; i < tokc; i++) {
if (!fstrstr(menu, item->text, tokv[i])) { if (!fstrstr(menu, item->text, tokv[i])) {
/* token does not match */ /* token does not match */
@ -337,11 +294,11 @@ static void match_items(struct menu *menu) {
continue; continue;
} }
if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) { 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)) { } else if (!menu->strncmp(tokv[0], item->text, tok_len)) {
append_match(item, &lprefix, &prefixend); append_item(item, &lprefix, &prefixend);
} else { } 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. // Read menu items from standard input.
void menu_render_items(struct menu *menu) { void read_menu_items(struct menu *menu) {
calc_widths(menu); char buf[sizeof menu->input];
match_items(menu);
render_menu(menu);
}
static void insert(struct menu *menu, const char *text, ssize_t len) { struct item **next = &menu->items;
if (strlen(menu->input) + len > sizeof menu->input - 1) { 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; return;
} }
memmove(menu->input + menu->cursor + len, menu->input + menu->cursor, item->text = strdup(buf);
sizeof menu->input - menu->cursor - MAX(len, 0));
if (len > 0 && text != NULL) { *next = item;
memcpy(menu->input + menu->cursor, text, len); next = &item->next;
}
menu->cursor += len;
} }
// Add pasted text to the menu input. calc_widths(menu);
void menu_paste(struct menu *menu, const char *text, ssize_t len) { match_items(menu);
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) { 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; return;
} }
struct xkb_state *state = context_get_xkb_state(menu->context); bool ctrl = xkb_state_mod_name_is_active(menu->keyboard->state,
bool ctrl = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_MOD_NAME_CTRL,
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 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); 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); XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
size_t len = strlen(menu->input); size_t len = strlen(menu->input);
@ -519,9 +490,32 @@ void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
return; return;
case XKB_KEY_Y: case XKB_KEY_Y:
// Paste clipboard // Paste clipboard
if (!context_paste(menu->context)) { if (!menu->data_offer) {
return; 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); match_items(menu);
render_menu(menu); render_menu(menu);
return; 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_Return:
case XKB_KEY_KP_Enter: case XKB_KEY_KP_Enter:
if (shift) { if (shift) {
menu->callback(menu, menu->input, true); puts(menu->input);
fflush(stdout);
menu->exit = true;
} else { } else {
char *text = menu->sel ? menu->sel->text : menu->input; char *text = menu->sel ? menu->sel->text : menu->input;
menu->callback(menu, text, !ctrl); puts(text);
fflush(stdout);
if (!ctrl) {
menu->exit = true;
}
} }
break; break;
case XKB_KEY_Left: 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 #ifndef WMENU_MENU_H
#define WMENU_MENU_H #define WMENU_MENU_H
#include <cairo/cairo.h>
#include <stdbool.h>
#include <sys/types.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <wayland-client.h>
struct menu; #include "pool-buffer.h"
typedef void (*menu_callback)(struct menu *menu, char *text, bool exit);
// A menu item. // A menu item.
struct item { struct item {
char *text; char *text;
int width; int width;
struct item *next; // traverses all items
struct item *prev_match; // previous matching item struct item *prev_match; // previous matching item
struct item *next_match; // next matching item struct item *next_match; // next matching item
struct page *page; // the page holding this item struct page *page; // the page holding this item
@ -27,14 +23,36 @@ struct page {
struct page *next; // next 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. // Menu state.
struct menu { struct menu {
// Whether the menu appears at the bottom of the screen // Whether the menu appears at the bottom of the screen
bool bottom; bool bottom;
// The function used to match menu items // The function used to match menu items
int (*strncmp)(const char *, const char *, size_t); int (*strncmp)(const char *, const char *, size_t);
// Whether the input is a password
bool passwd;
// The font used to display the menu // The font used to display the menu
char *font; char *font;
// The number of lines to list items vertically // The number of lines to list items vertically
@ -50,11 +68,24 @@ struct menu {
// Selection colors // Selection colors
uint32_t selectionbg, selectionfg; 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 struct keyboard *keyboard;
cairo_surface_t *test_surface; struct wl_data_device *data_device;
cairo_t *test_cairo; 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 width;
int height; int height;
@ -68,26 +99,25 @@ struct menu {
char input[BUFSIZ]; char input[BUFSIZ];
size_t cursor; size_t cursor;
struct item *items; // array of all items struct item *items; // list of all items
size_t item_count;
struct item *matches; // list of matching items struct item *matches; // list of matching items
struct item *matches_end; // last matching item struct item *matches_end; // last matching item
struct item *sel; // selected item struct item *sel; // selected item
struct page *pages; // list of pages struct page *pages; // list of pages
menu_callback callback;
bool exit; bool exit;
bool failure; bool failure;
}; };
struct menu *menu_create(menu_callback callback); struct menu *menu_create();
void menu_destroy(struct menu *menu); 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_getopts(struct menu *menu, int argc, char *argv[]);
void menu_add_item(struct menu *menu, char *text); void read_menu_items(struct menu *menu);
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, void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
xkb_keysym_t sym); xkb_keysym_t sym);
void menu_destroy(struct menu *menu);
#endif #endif

View file

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

View file

@ -12,7 +12,6 @@ endif
protocols = [ protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [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'], ['wlr-layer-shell-unstable-v1.xml'],
] ]

View file

@ -1,25 +1,13 @@
#define _POSIX_C_SOURCE 200809L
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
#include "render.h" #include "render.h"
#include "menu.h" #include "menu.h"
#include "pango.h" #include "pango.h"
#include "pool-buffer.h"
#include "wayland.h"
// Calculate text widths. // Calculate text widths.
void calc_widths(struct menu *menu) { void calc_widths(struct menu *menu) {
struct wl_context *context = menu->context; cairo_t *cairo = menu->current->cairo;
int scale = context_get_scale(context);
cairo_surface_set_device_scale(menu->test_surface, scale, scale);
cairo_set_antialias(menu->test_cairo, CAIRO_ANTIALIAS_BEST);
cairo_font_options_t *fo = cairo_font_options_create();
cairo_set_font_options(menu->test_cairo, fo);
cairo_font_options_destroy(fo);
cairo_t *cairo = menu->test_cairo;
// Calculate prompt width // Calculate prompt width
if (menu->prompt) { if (menu->prompt) {
@ -33,8 +21,7 @@ void calc_widths(struct menu *menu) {
menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding; menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding;
// Calculate item widths and input area width // Calculate item widths and input area width
for (size_t i = 0; i < menu->item_count; i++) { for (struct item *item = menu->items; item; item = item->next) {
struct item *item = &menu->items[i];
item->width = text_width(cairo, menu->font, item->text); item->width = text_width(cairo, menu->font, item->text);
if (item->width > menu->inputw) { if (item->width > menu->inputw) {
menu->inputw = item->width; menu->inputw = item->width;
@ -85,22 +72,8 @@ static void render_prompt(struct menu *menu, cairo_t *cairo) {
// Renders the input text. // Renders the input text.
static void render_input(struct menu *menu, cairo_t *cairo) { static void render_input(struct menu *menu, cairo_t *cairo) {
char *censort = NULL; render_text(menu, cairo, menu->input, menu->promptw, 0, 0,
0, menu->normalfg, menu->padding, menu->padding);
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. // 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. // Renders a single frame of the menu.
void render_menu(struct menu *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);
int scale = context_get_scale(context); cairo_t *cairo = cairo_create(recorder);
struct pool_buffer *buffer = context_get_next_buffer(context, scale); cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
if (!buffer) {
return;
}
cairo_t *shm = buffer->cairo;
cairo_set_antialias(shm, CAIRO_ANTIALIAS_BEST);
cairo_font_options_t *fo = cairo_font_options_create(); cairo_font_options_t *fo = cairo_font_options_create();
cairo_set_font_options(shm, fo); cairo_set_font_options(cairo, fo);
cairo_font_options_destroy(fo); cairo_font_options_destroy(fo);
render_to_cairo(menu, shm); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
cairo_paint(cairo);
cairo_restore(cairo);
struct wl_surface *surface = context_get_surface(context); render_to_cairo(menu, cairo);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer->buffer, 0, 0); int scale = menu->output ? menu->output->scale : 1;
wl_surface_damage(surface, 0, 0, menu->width, menu->height); menu->current = get_next_buffer(menu->shm,
wl_surface_commit(surface); menu->buffers, menu->width, menu->height, scale);
if (!menu->current) {
goto cleanup;
}
cairo_t *shm = menu->current->cairo;
cairo_save(shm);
cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR);
cairo_paint(shm);
cairo_restore(shm);
cairo_set_source_surface(shm, recorder, 0, 0);
cairo_paint(shm);
wl_surface_set_buffer_scale(menu->surface, scale);
wl_surface_attach(menu->surface, menu->current->buffer, 0, 0);
wl_surface_damage(menu->surface, 0, 0, menu->width, menu->height);
wl_surface_commit(menu->surface);
cleanup:
cairo_destroy(cairo);
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;
}