Initial commit - wmenu - 🔧 fork of wmenu
 (HTM) git clone git@git.drkhsh.at/wmenu.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 2f1c189d535c2d8dce74ec44c670305f00e4a30c
 (HTM) Author: adnano <me@adnano.co>
       Date:   Sun, 16 Jan 2022 08:32:58 -0500
       
       Initial commit
       
       Diffstat:
         A .gitignore                          |       1 +
         A LICENSE                             |      32 +++++++++++++++++++++++++++++++
         A README.md                           |      29 +++++++++++++++++++++++++++++
         A docs/meson.build                    |      26 ++++++++++++++++++++++++++
         A docs/wmenu.1.scd                    |      86 ++++++++++++++++++++++++++++++
         A main.c                              |     983 +++++++++++++++++++++++++++++++
         A meson.build                         |      54 +++++++++++++++++++++++++++++++
         A pango.c                             |      98 +++++++++++++++++++++++++++++++
         A pango.h                             |      18 ++++++++++++++++++
         A pool-buffer.c                       |     145 +++++++++++++++++++++++++++++++
         A pool-buffer.h                       |      21 +++++++++++++++++++++
         A protocols/meson.build               |      47 +++++++++++++++++++++++++++++++
         A protocols/wlr-layer-shell-unstable… |     285 +++++++++++++++++++++++++++++++
       
       13 files changed, 1825 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -0,0 +1 @@
       +build
 (DIR) diff --git a/LICENSE b/LICENSE
       @@ -0,0 +1,32 @@
       +MIT/X Consortium License
       +
       +© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
       +© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
       +© 2006-2007 Michał Janeczek <janeczek@gmail.com>
       +© 2007 Kris Maglione <jg@suckless.org>
       +© 2009 Gottox <gottox@s01.de>
       +© 2009 Markus Schnalke <meillo@marmaro.de>
       +© 2009 Evan Gates <evan.gates@gmail.com>
       +© 2010-2012 Connor Lane Smith <cls@lubutu.com>
       +© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org>
       +© 2015-2019 Quentin Rameau <quinq@fifth.space>
       +© 2018-2019 Henrik Nyman <h@nyymanni.com>
       +© 2022 adnano <me@adnano.co>
       +
       +Permission is hereby granted, free of charge, to any person obtaining a
       +copy of this software and associated documentation files (the "Software"),
       +to deal in the Software without restriction, including without limitation
       +the rights to use, copy, modify, merge, publish, distribute, sublicense,
       +and/or sell copies of the Software, and to permit persons to whom the
       +Software is furnished to do so, subject to the following conditions:
       +
       +The above copyright notice and this permission notice shall be included in
       +all copies or substantial portions of the Software.
       +
       +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
       +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
       +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
       +DEALINGS IN THE SOFTWARE.
 (DIR) diff --git a/README.md b/README.md
       @@ -0,0 +1,29 @@
       +# wmenu - dmenu for Wayland
       +
       +An efficient dynamic menu for supported Wayland compositors (requires
       +`wlr_layer_shell_v1` support).
       +
       +## Installation
       +
       +Dependencies:
       +
       +- cairo
       +- pango
       +- wayland
       +- xkbcommon
       +- scdoc (optional)
       +
       +```
       +$ meson build
       +$ ninja -C build
       +# ninja -C build install
       +```
       +
       +## Usage
       +
       +See wmenu(1)
       +
       +## 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.
 (DIR) diff --git a/docs/meson.build b/docs/meson.build
       @@ -0,0 +1,26 @@
       +scdoc_dep = dependency('scdoc', version: '>=1.9.2', native: true)
       +
       +if scdoc_dep.found()
       +        scdoc = find_program(
       +                scdoc_dep.get_pkgconfig_variable('scdoc'),
       +                native: true,
       +        )
       +        mandir = get_option('mandir')
       +
       +        docs = [
       +                'wmenu.1',
       +        ]
       +
       +        foreach path : docs
       +                custom_target(
       +                        path,
       +                        output: path,
       +                        input: '@0@.scd'.format(path),
       +                        capture: true,
       +                        feed: true,
       +                        command: [scdoc],
       +                        install: true,
       +                        install_dir: '@0@/man1'.format(mandir)
       +                )
       +        endforeach
       +endif
 (DIR) diff --git a/docs/wmenu.1.scd b/docs/wmenu.1.scd
       @@ -0,0 +1,86 @@
       +wmenu(1)
       +
       +# NAME
       +
       +wmenu - dynamic menu for Wayland
       +
       +# SYNOPSIS
       +
       +*wmenu* [-biv] \
       +  [-f _font_] \
       +  [-l _lines_] \
       +  [-o _output_] \
       +  [-p _prompt_] \
       +  [-N _color_] [-n _color_] \
       +  [-M _color_] [-m _color_] \
       +  [-S _color_] [-s _color_]
       +
       +# DESCRIPTION
       +
       +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.
       +
       +# OPTIONS
       +
       +*-b*
       +        wmenu appears at the bottom of the screen.
       +
       +*-i*
       +        wmenu matches menu items case insensitively.
       +
       +*-v*
       +        prints version information to stdout, then exits.
       +
       +*-f* _font_
       +        defines the font used. For more information, see
       +        https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
       +
       +*-l* _lines_
       +        wmenu lists items vertically, with the given number of lines.
       +
       +*-o* _output_
       +        wmenu is displayed on the output with the given name.
       +
       +*-p* _prompt_
       +        defines the prompt to be displayed to the left of the input field.
       +
       +*-N* _RRGGBB[AA]_
       +        defines the normal background color.
       +
       +*-n* _RRGGBB[AA]_
       +        defines the normal foreground color.
       +
       +*-M* _RRGGBB[AA]_
       +        defines the prompt background color.
       +
       +*-m* _RRGGBB[AA]_
       +        defines the prompt foreground color.
       +
       +*-S* _RRGGBB[AA]_
       +        defines the selection background color.
       +
       +*-s* _RRGGBB[AA]_
       +        defines the selection foreground color.
       +
       +# USAGE
       +
       +wmenu is completely controlled by the keyboard. Items are selected using the
       +arrow keys, page up, page down, home, and end.
       +
       +*Tab*
       +        Copy the selected item to the input field.
       +
       +*Return*
       +        Confirm selection. Prints the selected item to stdout and exits, returning
       +        success.
       +
       +*Ctrl-Return*
       +        Confirm selection. Prints the selected item to stdout and continues.
       +
       +*Shift-Return*
       +        Confirm input. Prints the input text to stdout and exits, returning success.
       +
       +*Escape*
       +        Exit without selecting an item, returning failure.
 (DIR) diff --git a/main.c b/main.c
       @@ -0,0 +1,983 @@
       +#define _POSIX_C_SOURCE 200809L
       +#include <assert.h>
       +#include <ctype.h>
       +#include <cairo/cairo.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 "pango.h"
       +#include "pool-buffer.h"
       +#include "wlr-layer-shell-unstable-v1-client-protocol.h"
       +#include "xdg-output-unstable-v1-client-protocol.h"
       +
       +struct menu_item {
       +        char *text;
       +        int width;
       +        struct menu_item *next;         // traverses all items
       +        struct menu_item *left, *right; // traverses matching items
       +};
       +
       +struct output {
       +        struct menu_state *menu;
       +        struct wl_output *output;
       +        struct zxdg_output_v1 *xdg_output;
       +        int32_t scale;
       +};
       +
       +struct menu_state {
       +        struct output *output;
       +        char *output_name;
       +
       +        struct wl_display *display;
       +        struct wl_compositor *compositor;
       +        struct wl_shm *shm;
       +        struct wl_seat *seat;
       +        struct wl_keyboard *keyboard;
       +        struct zwlr_layer_shell_v1 *layer_shell;
       +        struct zxdg_output_manager_v1 *output_manager;
       +
       +        struct wl_surface *surface;
       +        struct zwlr_layer_surface_v1 *layer_surface;
       +
       +        struct xkb_context *xkb_context;
       +        struct xkb_keymap *xkb_keymap;
       +        struct xkb_state *xkb_state;
       +
       +        struct pool_buffer buffers[2];
       +        struct pool_buffer *current;
       +
       +        int width;
       +        int height;
       +        int padding;
       +        int inputw;
       +        int promptw;
       +        int left_arrow, right_arrow;
       +
       +        bool bottom;
       +        int (*fstrncmp)(const char *, const char *, size_t);
       +        char *font;
       +        int lines;
       +        char *prompt;
       +        uint32_t background, foreground;
       +        uint32_t promptbg, promptfg;
       +        uint32_t selectionbg, selectionfg;
       +
       +        char text[BUFSIZ];
       +        size_t cursor;
       +
       +        int repeat_timer;
       +        int repeat_delay;
       +        int repeat_period;
       +        enum wl_keyboard_key_state repeat_key_state;
       +        xkb_keysym_t repeat_sym;
       +
       +        bool run;
       +        bool failure;
       +
       +        struct menu_item *items;
       +        struct menu_item *matches;
       +        struct menu_item *selection;
       +        struct menu_item *leftmost, *rightmost;
       +};
       +
       +static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
       +        cairo_set_source_rgba(cairo,
       +                        (color >> (3*8) & 0xFF) / 255.0,
       +                        (color >> (2*8) & 0xFF) / 255.0,
       +                        (color >> (1*8) & 0xFF) / 255.0,
       +                        (color >> (0*8) & 0xFF) / 255.0);
       +}
       +
       +static void insert(struct menu_state *state, const char *s, ssize_t n);
       +static void match(struct menu_state *state);
       +static size_t nextrune(struct menu_state *state, int incr);
       +
       +int render_text(struct menu_state *state, cairo_t *cairo, const char *str,
       +                int x, int y, int width, int height,
       +                uint32_t foreground, uint32_t background,
       +                int left_padding, int right_padding) {
       +
       +        int text_width, text_height;
       +        get_text_size(cairo, state->font, &text_width, &text_height, NULL, 1, str);
       +        int text_y = (state->height / 2.0) - (text_height / 2.0);
       +
       +        if (x + left_padding + text_width > width) {
       +                return -1;
       +        } else {
       +                if (background) {
       +                        int bg_width = text_width + left_padding + right_padding;
       +                        cairo_set_source_u32(cairo, background);
       +                        cairo_rectangle(cairo, x, 0, bg_width, height);
       +                        cairo_fill(cairo);
       +                }
       +
       +                cairo_move_to(cairo, x + left_padding, text_y);
       +                cairo_set_source_u32(cairo, foreground);
       +                pango_printf(cairo, state->font, 1, str);
       +        }
       +
       +        return x + text_width + left_padding + right_padding;
       +}
       +
       +void scroll_matches(struct menu_state *state) {
       +        if (!state->matches) {
       +                return;
       +        }
       +
       +        // Calculate available space
       +        int padding = state->padding;
       +        int width = state->width - state->inputw - state->promptw
       +                - state->left_arrow - state->right_arrow;
       +        if (state->leftmost == NULL) {
       +                state->leftmost = state->matches;
       +                if (state->rightmost == NULL) {
       +                        int offs = 0;
       +                        struct menu_item *item;
       +                        for (item = state->matches; item->left != state->selection; item = item->right) {
       +                                offs += item->width + 2 * padding;
       +                                if (offs >= width) {
       +                                        state->leftmost = item->left;
       +                                        offs = width - offs;
       +                                }
       +                        }
       +                } else {
       +                        int offs = 0;
       +                        struct menu_item *item;
       +                        for (item = state->rightmost; item; item = item->left) {
       +                                offs += item->width + 2 * padding;
       +                                if (offs >= width) {
       +                                        state->leftmost = item->right;
       +                                        break;
       +                                }
       +                        }
       +                }
       +        }
       +        if (state->rightmost == NULL) {
       +                state->rightmost = state->matches;
       +                int offs = 0;
       +                struct menu_item *item;
       +                for (item = state->leftmost; item; item = item->right) {
       +                        offs += item->width + 2 * padding;
       +                        if (offs >= width) {
       +                                break;
       +                        }
       +                        state->rightmost = item;
       +                }
       +        }
       +}
       +
       +void render_to_cairo(struct menu_state *state, cairo_t *cairo) {
       +        int width = state->width;
       +        int height = state->height;
       +        int padding = state->padding;
       +
       +        int text_height;
       +        get_text_size(cairo, state->font, NULL, &text_height, NULL, 1, "");
       +        int y = (height / 2.0) - (text_height / 2.0);
       +
       +        cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
       +        cairo_set_source_u32(cairo, state->background);
       +        cairo_paint(cairo);
       +
       +        int x = 0;
       +
       +        // Draw prompt
       +        if (state->prompt) {
       +                state->promptw = render_text(state, cairo, state->prompt,
       +                                0, y, state->width, state->height,
       +                                state->promptfg, state->promptbg,
       +                                padding, padding/2);
       +                x += state->promptw;
       +        }
       +
       +        // Draw background
       +        cairo_set_source_u32(cairo, state->background);
       +        cairo_rectangle(cairo, x, 0, 300, height);
       +        cairo_fill(cairo);
       +
       +        // Draw input
       +        render_text(state, cairo, state->text,
       +                        x, y, state->width, state->height,
       +                        state->foreground, 0, padding, padding);
       +
       +        // Draw cursor
       +        {
       +                int cursor_width = 2;
       +                int cursor_margin = 2;
       +                int cursor_pos = x + padding
       +                        + text_width(cairo, state->font, state->text)
       +                        - text_width(cairo, state->font, &state->text[state->cursor])
       +                        - cursor_width / 2;
       +                cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width,
       +                                state->height - 2 * cursor_margin);
       +                cairo_fill(cairo);
       +        }
       +
       +        if (!state->matches) {
       +                return;
       +        }
       +
       +        // Leave room for input
       +        x += state->inputw;
       +
       +        // Calculate scroll indicator widths
       +        state->left_arrow = text_width(cairo, state->font, "<") + 2 * padding;
       +        state->right_arrow = text_width(cairo, state->font, ">") + 2 * padding;
       +
       +        // Remember scroll indicator position
       +        int left_arrow_pos = x + padding;
       +        x += state->left_arrow;
       +
       +        // Draw matches
       +        bool scroll_right = false;
       +        struct menu_item *item;
       +        for (item = state->leftmost; item; item = item->right) {
       +                uint32_t bg_color = state->selection == item ? state->selectionbg : state->background;
       +                uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground;
       +                x = render_text(state, cairo, item->text,
       +                        x, y, width - state->right_arrow, height,
       +                        fg_color, bg_color, padding, padding);
       +                if (x == -1) {
       +                        scroll_right = true;
       +                        break;
       +                }
       +        }
       +
       +        // Draw left scroll indicator if necessary
       +        if (state->leftmost != state->matches) {
       +                cairo_move_to(cairo, left_arrow_pos, y);
       +                pango_printf(cairo, state->font, 1, "<");
       +        }
       +
       +        // Draw right scroll indicator if necessary
       +        if (scroll_right) {
       +                cairo_move_to(cairo, width - state->right_arrow + padding, y);
       +                pango_printf(cairo, state->font, 1, ">");
       +        }
       +}
       +
       +void render_frame(struct menu_state *state) {
       +        cairo_surface_t *recorder = cairo_recording_surface_create(
       +                        CAIRO_CONTENT_COLOR_ALPHA, NULL);
       +        cairo_t *cairo = cairo_create(recorder);
       +        cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
       +        cairo_font_options_t *fo = cairo_font_options_create();
       +        cairo_set_font_options(cairo, fo);
       +        cairo_font_options_destroy(fo);
       +        cairo_save(cairo);
       +        cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
       +        cairo_paint(cairo);
       +        cairo_restore(cairo);
       +
       +        render_to_cairo(state, cairo);
       +
       +        int scale = state->output ? state->output->scale : 1;
       +        state->current = get_next_buffer(state->shm,
       +                state->buffers, state->width, state->height, scale);
       +        if (!state->current) {
       +                goto cleanup;
       +        }
       +
       +        cairo_t *shm = state->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_attach(state->surface, state->current->buffer, 0, 0);
       +        wl_surface_damage(state->surface, 0, 0, state->width, state->height);
       +        wl_surface_commit(state->surface);
       +
       +cleanup:
       +        cairo_destroy(cairo);
       +}
       +
       +static void noop() {
       +        // Do nothing
       +}
       +
       +static void surface_enter(void *data, struct wl_surface *surface,
       +                struct wl_output *wl_output) {
       +        struct menu_state *state = data;
       +        state->output = wl_output_get_user_data(wl_output);
       +        wl_surface_set_buffer_scale(state->surface, state->output->scale);
       +        wl_surface_commit(state->surface);
       +        render_frame(state);
       +}
       +
       +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_state *state = data;
       +        state->width = width;
       +        state->height = height;
       +        zwlr_layer_surface_v1_ack_configure(surface, serial);
       +}
       +
       +static void layer_surface_closed(void *data,
       +                struct zwlr_layer_surface_v1 *surface) {
       +        struct menu_state *state = data;
       +        state->run = false;
       +}
       +
       +struct 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 zxdg_output_v1 *xdg_output,
       +                const char *name) {
       +        struct output *output = data;
       +        struct menu_state *state = output->menu;
       +        char *outname = state->output_name;
       +        if (!state->output && outname && strcmp(outname, name) == 0) {
       +                state->output = output;
       +        }
       +}
       +
       +struct wl_output_listener output_listener = {
       +        .geometry = noop,
       +        .mode = noop,
       +        .done = noop,
       +        .scale = output_scale,
       +};
       +
       +static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
       +                uint32_t format, int32_t fd, uint32_t size) {
       +        struct menu_state *state = data;
       +        if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
       +                close(fd);
       +                state->run = false;
       +                state->failure = true;
       +                return;
       +        }
       +        char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
       +        if (map_shm == MAP_FAILED) {
       +                close(fd);
       +                state->run = false;
       +                state->failure = true;
       +                return;
       +        }
       +        state->xkb_keymap = xkb_keymap_new_from_string(state->xkb_context,
       +                map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
       +        munmap(map_shm, size);
       +        close(fd);
       +        state->xkb_state = xkb_state_new(state->xkb_keymap);
       +}
       +
       +void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state,
       +                xkb_keysym_t sym) {
       +        if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) {
       +                return;
       +        }
       +
       +        bool ctrl = xkb_state_mod_name_is_active(state->xkb_state,
       +                        XKB_MOD_NAME_CTRL,
       +                        XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
       +        bool shift = xkb_state_mod_name_is_active(state->xkb_state,
       +                        XKB_MOD_NAME_SHIFT,
       +                        XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
       +
       +        char buf[8];
       +        size_t len = strlen(state->text);
       +        switch (sym) {
       +        case XKB_KEY_KP_Enter:
       +        case XKB_KEY_Return:
       +                if (shift) {
       +                        puts(state->text);
       +                        fflush(stdout);
       +                        state->run = false;
       +                } else {
       +                        char *text = state->selection ? state->selection->text
       +                                : state->text;
       +                        puts(text);
       +                        fflush(stdout);
       +                        if (!ctrl) {
       +                                state->run = false;
       +                        }
       +                }
       +                break;
       +        case XKB_KEY_Left:
       +                if (state->cursor && (!state->selection || !state->selection->left)) {
       +                        state->cursor = nextrune(state, -1);
       +                        render_frame(state);
       +                }
       +                if (state->selection && state->selection->left) {
       +                        if (state->selection == state->leftmost) {
       +                                state->rightmost = state->selection->left;
       +                                state->leftmost = NULL;
       +                        }
       +                        state->selection = state->selection->left;
       +                        scroll_matches(state);
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_Right:
       +                if (state->cursor < len) {
       +                        state->cursor = nextrune(state, +1);
       +                        render_frame(state);
       +                } else if (state->cursor == len) {
       +                        if (state->selection && state->selection->right) {
       +                                if (state->selection == state->rightmost) {
       +                                        state->leftmost = state->selection->right;
       +                                        state->rightmost = NULL;
       +                                }
       +                                state->selection = state->selection->right;
       +                                scroll_matches(state);
       +                                render_frame(state);
       +                        }
       +                }
       +                break;
       +        case XKB_KEY_Page_Up:
       +                if (state->leftmost && state->leftmost->left) {
       +                        state->rightmost = state->leftmost->left;
       +                        state->leftmost = NULL;
       +                        scroll_matches(state);
       +                        state->selection = state->leftmost;
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_Page_Down:
       +                if (state->rightmost && state->rightmost->right) {
       +                        state->leftmost = state->rightmost->right;
       +                        state->rightmost = NULL;
       +                        state->selection = state->leftmost;
       +                        scroll_matches(state);
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_Home:
       +                if (state->selection == state->matches) {
       +                        if (state->cursor != 0) {
       +                                state->cursor = 0;
       +                                render_frame(state);
       +                        }
       +                } else {
       +                        state->selection = state->matches;
       +                        state->leftmost = state->matches;
       +                        state->rightmost = NULL;
       +                        scroll_matches(state);
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_End:
       +                if (state->cursor < len) {
       +                        state->cursor = len;
       +                        render_frame(state);
       +                } else {
       +                        if (!state->selection || !state->selection->right) {
       +                                return;
       +                        }
       +                        while (state->selection && state->selection->right) {
       +                                state->selection = state->selection->right;
       +                        }
       +                        state->leftmost = NULL;
       +                        state->rightmost = state->selection;
       +                        scroll_matches(state);
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_BackSpace:
       +                if (state->cursor > 0) {
       +                        insert(state, NULL, nextrune(state, -1) - state->cursor);
       +                        render_frame(state);
       +                }
       +                break;
       +        case XKB_KEY_Delete:
       +                if (state->cursor == len) {
       +                        return;
       +                }
       +                state->cursor = nextrune(state, +1);
       +                insert(state, NULL, nextrune(state, -1) - state->cursor);
       +                render_frame(state);
       +                break;
       +        case XKB_KEY_Tab:
       +                if (!state->selection) {
       +                        return;
       +                }
       +                strncpy(state->text, state->selection->text, sizeof state->text);
       +                state->cursor = strlen(state->text);
       +                match(state);
       +                render_frame(state);
       +                break;
       +        case XKB_KEY_Escape:
       +                state->failure = true;
       +                state->run = false;
       +                break;
       +        default:
       +                if (xkb_keysym_to_utf8(sym, buf, 8)) {
       +                        insert(state, buf, strnlen(buf, 8));
       +                        render_frame(state);
       +                }
       +        }
       +}
       +
       +void keyboard_repeat(struct menu_state *state) {
       +        keypress(state, state->repeat_key_state, state->repeat_sym);
       +        struct itimerspec spec = { 0 };
       +        spec.it_value.tv_sec = state->repeat_period / 1000;
       +        spec.it_value.tv_nsec = (state->repeat_period % 1000) * 1000000l;
       +        timerfd_settime(state->repeat_timer, 0, &spec, NULL);
       +}
       +
       +static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
       +                uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) {
       +        struct menu_state *state = data;
       +
       +        enum wl_keyboard_key_state key_state = _key_state;
       +        xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb_state, key + 8);
       +        keypress(state, key_state, sym);
       +
       +        if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && state->repeat_period >= 0) {
       +                state->repeat_key_state = key_state;
       +                state->repeat_sym = sym;
       +
       +                struct itimerspec spec = { 0 };
       +                spec.it_value.tv_sec = state->repeat_delay / 1000;
       +                spec.it_value.tv_nsec = (state->repeat_delay % 1000) * 1000000l;
       +                timerfd_settime(state->repeat_timer, 0, &spec, NULL);
       +        } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) {
       +                struct itimerspec spec = { 0 };
       +                timerfd_settime(state->repeat_timer, 0, &spec, NULL);
       +        }
       +}
       +
       +static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
       +                int32_t rate, int32_t delay) {
       +        struct menu_state *state = data;
       +        state->repeat_delay = delay;
       +        if (rate > 0) {
       +                state->repeat_period = 1000 / rate;
       +        } else {
       +                state->repeat_period = -1;
       +        }
       +}
       +
       +static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard,
       +                uint32_t serial, uint32_t mods_depressed,
       +                uint32_t mods_latched, uint32_t mods_locked,
       +                uint32_t group) {
       +        struct menu_state *state = data;
       +        xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched,
       +                        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_state *state = data;
       +        if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
       +                state->keyboard = wl_seat_get_keyboard(seat);
       +                wl_keyboard_add_listener(state->keyboard, &keyboard_listener, state);
       +        }
       +}
       +
       +const struct wl_seat_listener seat_listener = {
       +        .capabilities = seat_capabilities,
       +        .name = noop,
       +};
       +
       +struct zxdg_output_v1_listener xdg_output_listener = {
       +        .logical_position = noop,
       +        .logical_size = noop,
       +        .done = noop,
       +        .name = output_name,
       +        .description = noop,
       +};
       +
       +static void handle_global(void *data, struct wl_registry *registry,
       +                uint32_t name, const char *interface, uint32_t version) {
       +        struct menu_state *state = data;
       +        if (strcmp(interface, wl_compositor_interface.name) == 0) {
       +                state->compositor = wl_registry_bind(registry, name,
       +                                &wl_compositor_interface, 4);
       +        } else if (strcmp(interface, wl_shm_interface.name) == 0) {
       +                state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
       +        } else if (strcmp(interface, wl_seat_interface.name) == 0) {
       +                struct wl_seat *seat = wl_registry_bind(registry, name,
       +                                &wl_seat_interface, 4);
       +                wl_seat_add_listener(seat, &seat_listener, state);
       +        } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
       +                state->layer_shell = wl_registry_bind(registry, name,
       +                                &zwlr_layer_shell_v1_interface, 1);
       +        } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
       +                state->output_manager = wl_registry_bind(registry, name,
       +                        &zxdg_output_manager_v1_interface, 3);
       +        } else if (strcmp(interface, wl_output_interface.name) == 0) {
       +                struct output *output = calloc(1, sizeof(struct output));
       +                output->output = wl_registry_bind(registry, name,
       +                                &wl_output_interface, 3);
       +                output->menu = state;
       +                output->scale = 1;
       +                wl_output_set_user_data(output->output, output);
       +                wl_output_add_listener(output->output, &output_listener, output);
       +                if (state->output_manager != NULL) {
       +                       output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
       +                               state->output_manager, output->output);
       +                       zxdg_output_v1_add_listener(output->xdg_output,
       +                               &xdg_output_listener, output);
       +                }
       +        }
       +}
       +
       +static const struct wl_registry_listener registry_listener = {
       +        .global = handle_global,
       +        .global_remove = noop,
       +};
       +
       +void insert(struct menu_state *state, const char *s, ssize_t n) {
       +        if (strlen(state->text) + n > sizeof state->text - 1) {
       +                return;
       +        }
       +        memmove(state->text + state->cursor + n, state->text + state->cursor,
       +                        sizeof state->text - state->cursor - MAX(n, 0));
       +        if (n > 0) {
       +                memcpy(state->text + state->cursor, s, n);
       +        }
       +        state->cursor += n;
       +        match(state);
       +}
       +
       +char * fstrstr(struct menu_state *state, const char *s, const char *sub) {
       +        size_t len;
       +
       +        for(len = strlen(sub); *s; s++)
       +                if(!state->fstrncmp(s, sub, len))
       +                        return (char *)s;
       +        return NULL;
       +}
       +
       +void append_item(struct menu_item *item, struct menu_item **list, struct menu_item **last) {
       +        if(!*last)
       +                *list = item;
       +        else
       +                (*last)->right = item;
       +        item->left = *last;
       +        item->right = NULL;
       +        *last = item;
       +}
       +
       +void match(struct menu_state *state) {
       +        struct menu_item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
       +
       +        state->matches = NULL;
       +        state->leftmost = NULL;
       +        size_t len = strlen(state->text);
       +        state->matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
       +        for (item = state->items; item; item = item->next) {
       +                if (!state->fstrncmp(state->text, item->text, len + 1)) {
       +                        append_item(item, &lexact, &exactend);
       +                } else if (!state->fstrncmp(state->text, item->text, len)) {
       +                        append_item(item, &lprefix, &prefixend);
       +                } else if (fstrstr(state, item->text, state->text)) {
       +                        append_item(item, &lsubstr, &substrend);
       +                }
       +        }
       +
       +        if (lexact) {
       +                state->matches = lexact;
       +                itemend = exactend;
       +        }
       +        if (lprefix) {
       +                if (itemend) {
       +                        itemend->right = lprefix;
       +                        lprefix->left = itemend;
       +                } else {
       +                        state->matches = lprefix;
       +                }
       +                itemend = prefixend;
       +        }
       +        if (lsubstr) {
       +                if (itemend) {
       +                        itemend->right = lsubstr;
       +                        lsubstr->left = itemend;
       +                        itemend = substrend;
       +                } else {
       +                        state->matches = lsubstr;
       +                }
       +        }
       +        state->selection = state->matches;
       +        state->leftmost = state->matches;
       +        state->rightmost = NULL;
       +        scroll_matches(state);
       +}
       +
       +size_t nextrune(struct menu_state *state, int incr) {
       +        size_t n, len;
       +
       +        len = strlen(state->text);
       +        for(n = state->cursor + incr; n < len && (state->text[n] & 0xc0) == 0x80; n += incr);
       +        return n;
       +}
       +
       +void read_stdin(struct menu_state *state) {
       +        char buf[sizeof state->text], *p;
       +        struct menu_item *item, **end;
       +
       +        for(end = &state->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) {
       +                if((p = strchr(buf, '\n'))) {
       +                        *p = '\0';
       +                }
       +                item = malloc(sizeof *item);
       +                if (!item) {
       +                        return;
       +                }
       +
       +                item->text = strdup(buf);
       +                item->next = item->left = item->right = NULL;
       +
       +                cairo_t *cairo = state->current->cairo;
       +                item->width = text_width(cairo, state->font, item->text);
       +                if (item->width > state->inputw) {
       +                        state->inputw = item->width;
       +                }
       +        }
       +}
       +
       +static void menu_init(struct menu_state *state) {
       +        int height = get_font_height(state->font);
       +        state->height = height + 2;
       +        state->padding = height / 2;
       +
       +        state->display = wl_display_connect(NULL);
       +        if (!state->display) {
       +                fprintf(stderr, "wl_display_connect: %s\n", strerror(errno));
       +                exit(EXIT_FAILURE);
       +        }
       +
       +        state->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
       +        if (!state->xkb_context) {
       +                fprintf(stderr, "xkb_context_new: %s\n", strerror(errno));
       +                exit(EXIT_FAILURE);
       +        }
       +
       +        state->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0);
       +        assert(state->repeat_timer >= 0);
       +
       +        struct wl_registry *registry = wl_display_get_registry(state->display);
       +        wl_registry_add_listener(registry, &registry_listener, state);
       +        wl_display_roundtrip(state->display);
       +        assert(state->compositor != NULL);
       +        assert(state->layer_shell != NULL);
       +        assert(state->shm != NULL);
       +        assert(state->output_manager != NULL);
       +
       +        // Second roundtrip for xdg-output
       +        wl_display_roundtrip(state->display);
       +
       +        if (state->output_name && !state->output) {
       +                fprintf(stderr, "Output %s not found\n", state->output_name);
       +                exit(EXIT_FAILURE);
       +        }
       +}
       +
       +static void menu_create_surface(struct menu_state *state) {
       +        state->surface = wl_compositor_create_surface(state->compositor);
       +        wl_surface_add_listener(state->surface, &surface_listener, state);
       +        state->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
       +                state->layer_shell,
       +                state->surface,
       +                NULL,
       +                ZWLR_LAYER_SHELL_V1_LAYER_TOP,
       +                "menu"
       +        );
       +        assert(state->layer_surface != NULL);
       +
       +        uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
       +                ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
       +        if (state->bottom) {
       +                anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
       +        } else {
       +                anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
       +        }
       +
       +        zwlr_layer_surface_v1_set_anchor(state->layer_surface, anchor);
       +        zwlr_layer_surface_v1_set_size(state->layer_surface, 0, state->height);
       +        zwlr_layer_surface_v1_set_exclusive_zone(state->layer_surface, -1);
       +        zwlr_layer_surface_v1_set_keyboard_interactivity(state->layer_surface, true);
       +        zwlr_layer_surface_v1_add_listener(state->layer_surface,
       +                &layer_surface_listener, state);
       +
       +        wl_surface_commit(state->surface);
       +        wl_display_roundtrip(state->display);
       +}
       +
       +bool parse_color(const char *color, uint32_t *result) {
       +        if (color[0] == '#') {
       +                ++color;
       +        }
       +        int len = strlen(color);
       +        if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) {
       +                return false;
       +        }
       +        char *ptr;
       +        uint32_t parsed = strtoul(color, &ptr, 16);
       +        if (*ptr != '\0') {
       +                return false;
       +        }
       +        *result = len == 6 ? ((parsed << 8) | 0xFF) : parsed;
       +        return true;
       +}
       +
       +int main(int argc, char **argv) {
       +        struct menu_state state = {
       +                .fstrncmp = strncmp,
       +                .font = "monospace 10",
       +                .background = 0x222222ff,
       +                .foreground = 0xbbbbbbff,
       +                .promptbg = 0x005577ff,
       +                .promptfg = 0xeeeeeeff,
       +                .selectionbg = 0x005577ff,
       +                .selectionfg = 0xeeeeeeff,
       +                .run = true,
       +        };
       +
       +        const char *usage =
       +                "Usage: wmenu [-biv] [-f font] [-l lines] [-o output] [-p prompt]\n"
       +                "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n";
       +
       +        int opt;
       +        while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) {
       +                switch (opt) {
       +                case 'b':
       +                        state.bottom = true;
       +                        break;
       +                case 'i':
       +                        state.fstrncmp = strncasecmp;
       +                        break;
       +                case 'v':
       +                        puts("wmenu " VERSION);
       +                        exit(EXIT_SUCCESS);
       +                case 'f':
       +                        state.font = optarg;
       +                        break;
       +                case 'l':
       +                        // TODO
       +                        fputs("warning: -l unimplemented\n", stderr);
       +                        state.lines = atoi(optarg);
       +                        break;
       +                case 'o':
       +                        state.output_name = optarg;
       +                        break;
       +                case 'p':
       +                        state.prompt = optarg;
       +                        break;
       +                case 'N':
       +                        if (!parse_color(optarg, &state.background)) {
       +                                fprintf(stderr, "Invalid background color: %s", optarg);
       +                        }
       +                        break;
       +                case 'n':
       +                        if (!parse_color(optarg, &state.foreground)) {
       +                                fprintf(stderr, "Invalid foreground color: %s", optarg);
       +                        }
       +                        break;
       +                case 'M':
       +                        if (!parse_color(optarg, &state.promptbg)) {
       +                                fprintf(stderr, "Invalid prompt background color: %s", optarg);
       +                        }
       +                        break;
       +                case 'm':
       +                        if (!parse_color(optarg, &state.promptfg)) {
       +                                fprintf(stderr, "Invalid prompt foreground color: %s", optarg);
       +                        }
       +                        break;
       +                case 'S':
       +                        if (!parse_color(optarg, &state.selectionbg)) {
       +                                fprintf(stderr, "Invalid selection background color: %s", optarg);
       +                        }
       +                        break;
       +                case 's':
       +                        if (!parse_color(optarg, &state.selectionfg)) {
       +                                fprintf(stderr, "Invalid selection foreground color: %s", optarg);
       +                        }
       +                        break;
       +                default:
       +                        fprintf(stderr, "%s", usage);
       +                        exit(EXIT_FAILURE);
       +                }
       +        }
       +
       +        if (optind < argc) {
       +                fprintf(stderr, "%s", usage);
       +                exit(EXIT_FAILURE);
       +        }
       +
       +        menu_init(&state);
       +        menu_create_surface(&state);
       +        render_frame(&state);
       +
       +        read_stdin(&state);
       +        match(&state);
       +
       +        struct pollfd fds[] = {
       +                { wl_display_get_fd(state.display), POLLIN },
       +                { state.repeat_timer, POLLIN },
       +        };
       +        const int nfds = sizeof(fds) / sizeof(*fds);
       +
       +        while (state.run) {
       +                errno = 0;
       +                do {
       +                        if (wl_display_flush(state.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(state.display) < 0) {
       +                                state.run = false;
       +                        }
       +                }
       +
       +                if (fds[1].revents & POLLIN) {
       +                        keyboard_repeat(&state);
       +                }
       +        }
       +
       +        wl_display_disconnect(state.display);
       +
       +        if (state.failure) {
       +                return EXIT_FAILURE;
       +        }
       +        return EXIT_SUCCESS;
       +}
 (DIR) diff --git a/meson.build b/meson.build
       @@ -0,0 +1,54 @@
       +project(
       +        'wmenu',
       +        'c',
       +        version: '0.1.0',
       +        license: 'MIT',
       +        default_options: [
       +                'c_std=c11',
       +                'warning_level=2',
       +                'werror=true',
       +        ]
       +)
       +
       +cc = meson.get_compiler('c')
       +
       +add_project_arguments(cc.get_supported_arguments([
       +        '-DVERSION="@0@"'.format(meson.project_version()),
       +
       +        '-Wno-missing-field-initializers',
       +        '-Wno-unused-parameter',
       +        '-Wundef',
       +        '-Wvla',
       +]), language : 'c')
       +
       +cairo           = dependency('cairo')
       +pango           = dependency('pango')
       +pangocairo      = dependency('pangocairo')
       +wayland_client  = dependency('wayland-client')
       +wayland_protos  = dependency('wayland-protocols')
       +xkbcommon       = dependency('xkbcommon')
       +
       +rt = cc.find_library('rt')
       +
       +subdir('protocols')
       +subdir('docs')
       +
       +executable(
       +        'wmenu',
       +        files(
       +                'main.c',
       +                'pango.c',
       +                'pool-buffer.c',
       +        ),
       +        dependencies: [
       +                cairo,
       +                client_protos,
       +                pango,
       +                pangocairo,
       +                rt,
       +                wayland_client,
       +                wayland_protos,
       +                xkbcommon,
       +        ],
       +        install: true,
       +)
 (DIR) diff --git a/pango.c b/pango.c
       @@ -0,0 +1,98 @@
       +#include <cairo/cairo.h>
       +#include <pango/pangocairo.h>
       +#include <stdarg.h>
       +#include <stdbool.h>
       +#include <stdint.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +
       +int get_font_height(char *fontstr) {
       +        PangoFontMap *fontmap = pango_cairo_font_map_get_default();
       +        PangoContext *context = pango_font_map_create_context(fontmap);
       +        PangoFontDescription *desc = pango_font_description_from_string(fontstr);
       +        PangoFont *font = pango_font_map_load_font(fontmap, context, desc);
       +        if (font == NULL) {
       +                return -1;
       +        }
       +        PangoFontMetrics *metrics = pango_font_get_metrics(font, NULL);
       +        int height = pango_font_metrics_get_height(metrics) / PANGO_SCALE;
       +        pango_font_description_free(desc);
       +        pango_font_metrics_unref(metrics);
       +        return height;
       +}
       +
       +PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
       +                const char *text, double scale) {
       +        PangoLayout *layout = pango_cairo_create_layout(cairo);
       +        PangoAttrList *attrs = pango_attr_list_new();
       +        pango_layout_set_text(layout, text, -1);
       +        pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
       +        PangoFontDescription *desc = pango_font_description_from_string(font);
       +        pango_layout_set_font_description(layout, desc);
       +        pango_layout_set_single_paragraph_mode(layout, 1);
       +        pango_layout_set_attributes(layout, attrs);
       +        pango_attr_list_unref(attrs);
       +        pango_font_description_free(desc);
       +        return layout;
       +}
       +
       +void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
       +                int *baseline, double scale, const char *fmt, ...) {
       +        va_list args;
       +        va_start(args, fmt);
       +        // Add one since vsnprintf excludes null terminator.
       +        int length = vsnprintf(NULL, 0, fmt, args) + 1;
       +        va_end(args);
       +
       +        char *buf = malloc(length);
       +        if (buf == NULL) {
       +                return;
       +        }
       +        va_start(args, fmt);
       +        vsnprintf(buf, length, fmt, args);
       +        va_end(args);
       +
       +        PangoLayout *layout = get_pango_layout(cairo, font, buf, scale);
       +        pango_cairo_update_layout(cairo, layout);
       +        pango_layout_get_pixel_size(layout, width, height);
       +        if (baseline) {
       +                *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
       +        }
       +
       +        g_object_unref(layout);
       +        free(buf);
       +}
       +
       +int text_width(cairo_t *cairo, const char *font, const char *text) {
       +        int text_width;
       +        get_text_size(cairo, font, &text_width, NULL, NULL, 1, text);
       +        return text_width;
       +}
       +
       +void pango_printf(cairo_t *cairo, const char *font, double scale,
       +                const char *fmt, ...) {
       +        va_list args;
       +        va_start(args, fmt);
       +        // Add one since vsnprintf excludes null terminator.
       +        int length = vsnprintf(NULL, 0, fmt, args) + 1;
       +        va_end(args);
       +
       +        char *buf = malloc(length);
       +        if (buf == NULL) {
       +                return;
       +        }
       +        va_start(args, fmt);
       +        vsnprintf(buf, length, fmt, args);
       +        va_end(args);
       +
       +        PangoLayout *layout = get_pango_layout(cairo, font, buf, scale);
       +        cairo_font_options_t *fo = cairo_font_options_create();
       +        cairo_get_font_options(cairo, fo);
       +        pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo);
       +        cairo_font_options_destroy(fo);
       +        pango_cairo_update_layout(cairo, layout);
       +        pango_cairo_show_layout(cairo, layout);
       +        g_object_unref(layout);
       +        free(buf);
       +}
 (DIR) diff --git a/pango.h b/pango.h
       @@ -0,0 +1,18 @@
       +#ifndef DMENU_PANGO_H
       +#define DMENU_PANGO_H
       +#include <stdarg.h>
       +#include <stdbool.h>
       +#include <stdint.h>
       +#include <cairo/cairo.h>
       +#include <pango/pangocairo.h>
       +
       +int get_font_height(const char *font);
       +PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
       +                const char *text, double scale);
       +void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
       +                int *baseline, double scale, const char *fmt, ...);
       +int text_width(cairo_t *cairo, const char *font, const char *text);
       +void pango_printf(cairo_t *cairo, const char *font, double scale,
       +                const char *fmt, ...);
       +
       +#endif
 (DIR) diff --git a/pool-buffer.c b/pool-buffer.c
       @@ -0,0 +1,145 @@
       +#define _POSIX_C_SOURCE 200809L
       +#include <assert.h>
       +#include <cairo.h>
       +#include <errno.h>
       +#include <fcntl.h>
       +#include <pango/pangocairo.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/mman.h>
       +#include <time.h>
       +#include <unistd.h>
       +#include <wayland-client.h>
       +#include "pool-buffer.h"
       +
       +static void randname(char *buf) {
       +        struct timespec ts;
       +        clock_gettime(CLOCK_REALTIME, &ts);
       +        long r = ts.tv_nsec;
       +        for (int i = 0; i < 6; ++i) {
       +                buf[i] = 'A'+(r&15)+(r&16)*2;
       +                r >>= 5;
       +        }
       +}
       +
       +static int anonymous_shm_open(void) {
       +        char name[] = "/wmenu-XXXXXX";
       +        int retries = 100;
       +
       +        do {
       +                randname(name + strlen(name) - 6);
       +
       +                --retries;
       +                // shm_open guarantees that O_CLOEXEC is set
       +                int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
       +                if (fd >= 0) {
       +                        shm_unlink(name);
       +                        return fd;
       +                }
       +        } while (retries > 0 && errno == EEXIST);
       +
       +        return -1;
       +}
       +
       +static int create_shm_file(off_t size) {
       +        int fd = anonymous_shm_open();
       +        if (fd < 0) {
       +                return fd;
       +        }
       +
       +        if (ftruncate(fd, size) < 0) {
       +                close(fd);
       +                return -1;
       +        }
       +
       +        return fd;
       +}
       +
       +static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
       +        struct pool_buffer *buffer = data;
       +        buffer->busy = false;
       +}
       +
       +static const struct wl_buffer_listener buffer_listener = {
       +        .release = buffer_release
       +};
       +
       +static struct pool_buffer *create_buffer(struct wl_shm *shm,
       +                struct pool_buffer *buf, int32_t width, int32_t height,
       +                int32_t scale, uint32_t format) {
       +        uint32_t stride = width * scale * 4;
       +        size_t size = stride * height * scale;
       +
       +        int fd = create_shm_file(size);
       +        assert(fd != -1);
       +        void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
       +        struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
       +        buf->buffer = wl_shm_pool_create_buffer(pool, 0,
       +                        width * scale, height * scale, stride, format);
       +        wl_shm_pool_destroy(pool);
       +        close(fd);
       +
       +        buf->size = size;
       +        buf->width = width;
       +        buf->height = height;
       +        buf->scale = scale;
       +        buf->data = data;
       +        buf->surface = cairo_image_surface_create_for_data(data,
       +                        CAIRO_FORMAT_ARGB32, width * scale, height * scale, stride);
       +        cairo_surface_set_device_scale(buf->surface, scale, scale);
       +        buf->cairo = cairo_create(buf->surface);
       +        buf->pango = pango_cairo_create_context(buf->cairo);
       +
       +        wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
       +        return buf;
       +}
       +
       +void destroy_buffer(struct pool_buffer *buffer) {
       +        if (buffer->buffer) {
       +                wl_buffer_destroy(buffer->buffer);
       +        }
       +        if (buffer->cairo) {
       +                cairo_destroy(buffer->cairo);
       +        }
       +        if (buffer->surface) {
       +                cairo_surface_destroy(buffer->surface);
       +        }
       +        if (buffer->pango) {
       +                g_object_unref(buffer->pango);
       +        }
       +        if (buffer->data) {
       +                munmap(buffer->data, buffer->size);
       +        }
       +        memset(buffer, 0, sizeof(struct pool_buffer));
       +}
       +
       +struct pool_buffer *get_next_buffer(struct wl_shm *shm,
       +                struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale) {
       +        struct pool_buffer *buffer = NULL;
       +
       +        for (size_t i = 0; i < 2; ++i) {
       +                if (pool[i].busy) {
       +                        continue;
       +                }
       +                buffer = &pool[i];
       +        }
       +
       +        if (!buffer) {
       +                return NULL;
       +        }
       +
       +        if (buffer->width != width || buffer->height != height
       +                        || buffer->scale != scale) {
       +                destroy_buffer(buffer);
       +        }
       +
       +        if (!buffer->buffer) {
       +                if (!create_buffer(shm, buffer, width, height, scale,
       +                                        WL_SHM_FORMAT_ARGB8888)) {
       +                        return NULL;
       +                }
       +        }
       +        buffer->busy = true;
       +        return buffer;
       +}
 (DIR) diff --git a/pool-buffer.h b/pool-buffer.h
       @@ -0,0 +1,21 @@
       +/* Taken from sway. MIT licensed */
       +#include <cairo.h>
       +#include <pango/pangocairo.h>
       +#include <stdbool.h>
       +#include <stdint.h>
       +#include <wayland-client.h>
       +
       +struct pool_buffer {
       +        struct wl_buffer *buffer;
       +        cairo_surface_t *surface;
       +        cairo_t *cairo;
       +        PangoContext *pango;
       +        size_t size;
       +        int32_t width, height, scale;
       +        void *data;
       +        bool busy;
       +};
       +
       +struct pool_buffer *get_next_buffer(struct wl_shm *shm,
       +                struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale);
       +void destroy_buffer(struct pool_buffer *buffer);
 (DIR) diff --git a/protocols/meson.build b/protocols/meson.build
       @@ -0,0 +1,47 @@
       +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
       +
       +wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true)
       +if wayland_scanner_dep.found()
       +        wayland_scanner = find_program(
       +                wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'),
       +                native: true,
       +        )
       +else
       +        wayland_scanner = find_program('wayland-scanner', native: true)
       +endif
       +
       +protocols = [
       +        [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
       +        [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
       +        ['wlr-layer-shell-unstable-v1.xml'],
       +]
       +
       +wl_protos_src = []
       +wl_protos_headers = []
       +
       +foreach p : protocols
       +        xml = join_paths(p)
       +        wl_protos_src += custom_target(
       +                xml.underscorify() + '_protocol_c',
       +                input: xml,
       +                output: '@BASENAME@-protocol.c',
       +                command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
       +        )
       +        wl_protos_headers += custom_target(
       +                xml.underscorify() + '_client_h',
       +                input: xml,
       +                output: '@BASENAME@-client-protocol.h',
       +                command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
       +        )
       +endforeach
       +
       +lib_client_protos = static_library(
       +        'client_protos',
       +        wl_protos_src + wl_protos_headers,
       +        dependencies: wayland_client.partial_dependency(compile_args: true),
       +)
       +
       +client_protos = declare_dependency(
       +        link_with: lib_client_protos,
       +        sources: wl_protos_headers,
       +)
 (DIR) diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml
       @@ -0,0 +1,285 @@
       +<?xml version="1.0" encoding="UTF-8"?>
       +<protocol name="wlr_layer_shell_unstable_v1">
       +  <copyright>
       +    Copyright © 2017 Drew DeVault
       +
       +    Permission to use, copy, modify, distribute, and sell this
       +    software and its documentation for any purpose is hereby granted
       +    without fee, provided that the above copyright notice appear in
       +    all copies and that both that copyright notice and this permission
       +    notice appear in supporting documentation, and that the name of
       +    the copyright holders not be used in advertising or publicity
       +    pertaining to distribution of the software without specific,
       +    written prior permission.  The copyright holders make no
       +    representations about the suitability of this software for any
       +    purpose.  It is provided "as is" without express or implied
       +    warranty.
       +
       +    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
       +    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
       +    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
       +    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       +    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
       +    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
       +    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
       +    THIS SOFTWARE.
       +  </copyright>
       +
       +  <interface name="zwlr_layer_shell_v1" version="1">
       +    <description summary="create surfaces that are layers of the desktop">
       +      Clients can use this interface to assign the surface_layer role to
       +      wl_surfaces. Such surfaces are assigned to a "layer" of the output and
       +      rendered with a defined z-depth respective to each other. They may also be
       +      anchored to the edges and corners of a screen and specify input handling
       +      semantics. This interface should be suitable for the implementation of
       +      many desktop shell components, and a broad number of other applications
       +      that interact with the desktop.
       +    </description>
       +
       +    <request name="get_layer_surface">
       +      <description summary="create a layer_surface from a surface">
       +        Create a layer surface for an existing surface. This assigns the role of
       +        layer_surface, or raises a protocol error if another role is already
       +        assigned.
       +
       +        Creating a layer surface from a wl_surface which has a buffer attached
       +        or committed is a client error, and any attempts by a client to attach
       +        or manipulate a buffer prior to the first layer_surface.configure call
       +        must also be treated as errors.
       +
       +        You may pass NULL for output to allow the compositor to decide which
       +        output to use. Generally this will be the one that the user most
       +        recently interacted with.
       +
       +        Clients can specify a namespace that defines the purpose of the layer
       +        surface.
       +      </description>
       +      <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
       +      <arg name="surface" type="object" interface="wl_surface"/>
       +      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
       +      <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
       +      <arg name="namespace" type="string" summary="namespace for the layer surface"/>
       +    </request>
       +
       +    <enum name="error">
       +      <entry name="role" value="0" summary="wl_surface has another role"/>
       +      <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
       +      <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
       +    </enum>
       +
       +    <enum name="layer">
       +      <description summary="available layers for surfaces">
       +        These values indicate which layers a surface can be rendered in. They
       +        are ordered by z depth, bottom-most first. Traditional shell surfaces
       +        will typically be rendered between the bottom and top layers.
       +        Fullscreen shell surfaces are typically rendered at the top layer.
       +        Multiple surfaces can share a single layer, and ordering within a
       +        single layer is undefined.
       +      </description>
       +
       +      <entry name="background" value="0"/>
       +      <entry name="bottom" value="1"/>
       +      <entry name="top" value="2"/>
       +      <entry name="overlay" value="3"/>
       +    </enum>
       +  </interface>
       +
       +  <interface name="zwlr_layer_surface_v1" version="1">
       +    <description summary="layer metadata interface">
       +      An interface that may be implemented by a wl_surface, for surfaces that
       +      are designed to be rendered as a layer of a stacked desktop-like
       +      environment.
       +
       +      Layer surface state (size, anchor, exclusive zone, margin, interactivity)
       +      is double-buffered, and will be applied at the time wl_surface.commit of
       +      the corresponding wl_surface is called.
       +    </description>
       +
       +    <request name="set_size">
       +      <description summary="sets the size of the surface">
       +        Sets the size of the surface in surface-local coordinates. The
       +        compositor will display the surface centered with respect to its
       +        anchors.
       +
       +        If you pass 0 for either value, the compositor will assign it and
       +        inform you of the assignment in the configure event. You must set your
       +        anchor to opposite edges in the dimensions you omit; not doing so is a
       +        protocol error. Both values are 0 by default.
       +
       +        Size is double-buffered, see wl_surface.commit.
       +      </description>
       +      <arg name="width" type="uint"/>
       +      <arg name="height" type="uint"/>
       +    </request>
       +
       +    <request name="set_anchor">
       +      <description summary="configures the anchor point of the surface">
       +        Requests that the compositor anchor the surface to the specified edges
       +        and corners. If two orthoginal edges are specified (e.g. 'top' and
       +        'left'), then the anchor point will be the intersection of the edges
       +        (e.g. the top left corner of the output); otherwise the anchor point
       +        will be centered on that edge, or in the center if none is specified.
       +
       +        Anchor is double-buffered, see wl_surface.commit.
       +      </description>
       +      <arg name="anchor" type="uint" enum="anchor"/>
       +    </request>
       +
       +    <request name="set_exclusive_zone">
       +      <description summary="configures the exclusive geometry of this surface">
       +        Requests that the compositor avoids occluding an area of the surface
       +        with other surfaces. The compositor's use of this information is
       +        implementation-dependent - do not assume that this region will not
       +        actually be occluded.
       +
       +        A positive value is only meaningful if the surface is anchored to an
       +        edge, rather than a corner. The zone is the number of surface-local
       +        coordinates from the edge that are considered exclusive.
       +
       +        Surfaces that do not wish to have an exclusive zone may instead specify
       +        how they should interact with surfaces that do. If set to zero, the
       +        surface indicates that it would like to be moved to avoid occluding
       +        surfaces with a positive excluzive zone. If set to -1, the surface
       +        indicates that it would not like to be moved to accommodate for other
       +        surfaces, and the compositor should extend it all the way to the edges
       +        it is anchored to.
       +
       +        For example, a panel might set its exclusive zone to 10, so that
       +        maximized shell surfaces are not shown on top of it. A notification
       +        might set its exclusive zone to 0, so that it is moved to avoid
       +        occluding the panel, but shell surfaces are shown underneath it. A
       +        wallpaper or lock screen might set their exclusive zone to -1, so that
       +        they stretch below or over the panel.
       +
       +        The default value is 0.
       +
       +        Exclusive zone is double-buffered, see wl_surface.commit.
       +      </description>
       +      <arg name="zone" type="int"/>
       +    </request>
       +
       +    <request name="set_margin">
       +      <description summary="sets a margin from the anchor point">
       +        Requests that the surface be placed some distance away from the anchor
       +        point on the output, in surface-local coordinates. Setting this value
       +        for edges you are not anchored to has no effect.
       +
       +        The exclusive zone includes the margin.
       +
       +        Margin is double-buffered, see wl_surface.commit.
       +      </description>
       +      <arg name="top" type="int"/>
       +      <arg name="right" type="int"/>
       +      <arg name="bottom" type="int"/>
       +      <arg name="left" type="int"/>
       +    </request>
       +
       +    <request name="set_keyboard_interactivity">
       +      <description summary="requests keyboard events">
       +        Set to 1 to request that the seat send keyboard events to this layer
       +        surface. For layers below the shell surface layer, the seat will use
       +        normal focus semantics. For layers above the shell surface layers, the
       +        seat will always give exclusive keyboard focus to the top-most layer
       +        which has keyboard interactivity set to true.
       +
       +        Layer surfaces receive pointer, touch, and tablet events normally. If
       +        you do not want to receive them, set the input region on your surface
       +        to an empty region.
       +
       +        Events is double-buffered, see wl_surface.commit.
       +      </description>
       +      <arg name="keyboard_interactivity" type="uint"/>
       +    </request>
       +
       +    <request name="get_popup">
       +      <description summary="assign this layer_surface as an xdg_popup parent">
       +        This assigns an xdg_popup's parent to this layer_surface.  This popup
       +        should have been created via xdg_surface::get_popup with the parent set
       +        to NULL, and this request must be invoked before committing the popup's
       +        initial state.
       +
       +        See the documentation of xdg_popup for more details about what an
       +        xdg_popup is and how it is used.
       +      </description>
       +      <arg name="popup" type="object" interface="xdg_popup"/>
       +    </request>
       +
       +    <request name="ack_configure">
       +      <description summary="ack a configure event">
       +        When a configure event is received, if a client commits the
       +        surface in response to the configure event, then the client
       +        must make an ack_configure request sometime before the commit
       +        request, passing along the serial of the configure event.
       +
       +        If the client receives multiple configure events before it
       +        can respond to one, it only has to ack the last configure event.
       +
       +        A client is not required to commit immediately after sending
       +        an ack_configure request - it may even ack_configure several times
       +        before its next surface commit.
       +
       +        A client may send multiple ack_configure requests before committing, but
       +        only the last request sent before a commit indicates which configure
       +        event the client really is responding to.
       +      </description>
       +      <arg name="serial" type="uint" summary="the serial from the configure event"/>
       +    </request>
       +
       +    <request name="destroy" type="destructor">
       +      <description summary="destroy the layer_surface">
       +        This request destroys the layer surface.
       +      </description>
       +    </request>
       +
       +    <event name="configure">
       +      <description summary="suggest a surface change">
       +        The configure event asks the client to resize its surface.
       +
       +        Clients should arrange their surface for the new states, and then send
       +        an ack_configure request with the serial sent in this configure event at
       +        some point before committing the new surface.
       +
       +        The client is free to dismiss all but the last configure event it
       +        received.
       +
       +        The width and height arguments specify the size of the window in
       +        surface-local coordinates.
       +
       +        The size is a hint, in the sense that the client is free to ignore it if
       +        it doesn't resize, pick a smaller size (to satisfy aspect ratio or
       +        resize in steps of NxM pixels). If the client picks a smaller size and
       +        is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
       +        surface will be centered on this axis.
       +
       +        If the width or height arguments are zero, it means the client should
       +        decide its own window dimension.
       +      </description>
       +      <arg name="serial" type="uint"/>
       +      <arg name="width" type="uint"/>
       +      <arg name="height" type="uint"/>
       +    </event>
       +
       +    <event name="closed">
       +      <description summary="surface should be closed">
       +        The closed event is sent by the compositor when the surface will no
       +        longer be shown. The output may have been destroyed or the user may
       +        have asked for it to be removed. Further changes to the surface will be
       +        ignored. The client should destroy the resource after receiving this
       +        event, and create a new surface if they so choose.
       +      </description>
       +    </event>
       +
       +    <enum name="error">
       +      <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
       +      <entry name="invalid_size" value="1" summary="size is invalid"/>
       +      <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
       +    </enum>
       +
       +    <enum name="anchor" bitfield="true">
       +      <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
       +      <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
       +      <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
       +      <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
       +    </enum>
       +  </interface>
       +</protocol>