commit 6cef260109af45bae5ffe053eec81f064620d6e2
Author: sewn <sewn@disroot.org>
Date: Wed, 26 Jun 2024 07:58:41 +0300
initial commit
Diffstat:
A | .gitignore | | | 4 | ++++ |
A | LICENSE | | | 34 | ++++++++++++++++++++++++++++++++++ |
A | Makefile | | | 69 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | 22 | ++++++++++++++++++++++ |
A | config.def.h | | | 21 | +++++++++++++++++++++ |
A | drwl.h | | | 306 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | mew-run | | | 35 | +++++++++++++++++++++++++++++++++++ |
A | mew.1 | | | 152 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | mew.c | | | 921 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | poolbuf.h | | | 112 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | wlr-layer-shell-unstable-v1.xml | | | 285 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 1961 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+mew
+config.h
+*.o
+*-protocol.*
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,34 @@
+MIT/X Consortium License
+
+Copyright (c) 2023-2024 sewn <sewn@disroot.org>
+Copyright (c) 2015 Eric Pruitt <eric.pruitt@gmail.com>
+Copyright (c) 2024 Forrest Bushstone <fgb.1@protonmail.com>
+Copyright (c) 2018-2019 Henrik Nyman <h@nyymanni.com>
+Copyright (c) 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+Copyright (c) 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
+Copyright (c) 2006-2007 Michał Janeczek <janeczek@gmail.com>
+Copyright (c) 2007 Kris Maglione <jg@suckless.org>
+Copyright (c) 2009 Gottox <gottox@s01.de>
+Copyright (c) 2009 Markus Schnalke <meillo@marmaro.de>
+Copyright (c) 2009 Evan Gates <evan.gates@gmail.com>
+Copyright (c) 2010-2012 Connor Lane Smith <cls@lubutu.com>
+Copyright (c) 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
+Copyright (c) 2015-2019 Quentin Rameau <quinq@fifth.space>
+
+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.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,69 @@
+# mew - dynamic menu
+# See LICENSE file for copyright and license details.
+.POSIX:
+
+# mew version
+VERSION = 1.0
+
+# pkg-config
+PKG_CONFIG = pkg-config
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+# includes and libs
+PKGS = fcft pixman-1 wayland-client xkbcommon
+INCS = `$(PKG_CONFIG) --cflags $(PKGS)`
+LIBS = `$(PKG_CONFIG) --libs $(PKGS)`
+
+# flags
+EMCPPFLAGS = -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\"
+EMCFLAGS = -pedantic -Wall $(INCS) $(EMCPPFLAGS) $(CFLAGS)
+LDLIBS = $(LIBS)
+
+all: mew
+
+.c.o:
+ $(CC) -c $(EMCFLAGS) $<
+
+config.h:
+ cp config.def.h $@
+
+mew.o: config.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h
+
+mew: wlr-layer-shell-unstable-v1-protocol.o xdg-shell-protocol.o mew.o
+ $(CC) $(LDFLAGS) -o $@ wlr-layer-shell-unstable-v1-protocol.o xdg-shell-protocol.o mew.o $(LDLIBS)
+
+WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols`
+WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner`
+
+xdg-shell-protocol.h:
+ $(WAYLAND_SCANNER) client-header $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+xdg-shell-protocol.c:
+ $(WAYLAND_SCANNER) private-code $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+
+wlr-layer-shell-unstable-v1-protocol.h:
+ $(WAYLAND_SCANNER) client-header wlr-layer-shell-unstable-v1.xml $@
+wlr-layer-shell-unstable-v1-protocol.c:
+ $(WAYLAND_SCANNER) private-code wlr-layer-shell-unstable-v1.xml $@
+wlr-layer-shell-unstable-v1-protocol.o: xdg-shell-protocol.o
+
+clean:
+ rm -f mew *.o *-protocol.*
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f mew mew-run $(DESTDIR)$(PREFIX)/bin
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/mew
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/mew-run
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ sed "s/VERSION/$(VERSION)/g" < mew.1 > $(DESTDIR)$(MANPREFIX)/man1/mew.1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/mew.1
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/mew\
+ $(DESTDIR)$(PREFIX)/bin/mew-run\
+ $(DESTDIR)$(MANPREFIX)/man1/mew.1
+
+.PHONY: all clean install uninstall
diff --git a/README.md b/README.md
@@ -0,0 +1,21 @@
+# mew
+mew is a simple menu for Wayland.
+
+## Building
+In order to build mew, ensure that you have the following dependencies:
+
+* fcft
+* pkg-config
+* wayland
+* wayland-protocols
+* xkbcommon
+
+Afterwards enter the following command to build and install mew
+(if necessary as root):
+```
+make
+make install
+```
+
+# Usage
+See the man page for details.
+\ No newline at end of file
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,21 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+
+static int top = 1; /* -b option; if 0, appear at bottom */
+static const char *fonts[] = { "monospace:size=10" }; /* -f option overrides fonts[0] */
+static const char *prompt = NULL; /* -p option; prompt to the left of input field */
+static uint32_t colors[][2] = {
+ /* fg bg */
+ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff },
+ [SchemeSel] = { 0xeeeeeeff, 0x005577ff },
+ [SchemeOut] = { 0x000000ff, 0x00ffffff },
+};
+
+/* -l option; if nonzero, use vertical list with given number of lines */
+static unsigned int lines = 0;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * for example: " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
diff --git a/drwl.h b/drwl.h
@@ -0,0 +1,306 @@
+/*
+ * drwl - https://codeberg.org/sewn/drwl
+ * See LICENSE file for copyright and license details.
+ */
+#pragma once
+
+#include <stdlib.h>
+#include <fcft/fcft.h>
+#include <pixman-1/pixman.h>
+
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+enum { ColFg, ColBg }; /* colorscheme index */
+
+typedef struct {
+ pixman_image_t *pix;
+ struct fcft_font *font;
+ uint32_t *scheme;
+} Drwl;
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const uint32_t utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const uint32_t utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static inline uint32_t
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static inline size_t
+utf8decode(const char *c, uint32_t *u)
+{
+ size_t i, j, len, type;
+ uint32_t udecoded;
+
+ *u = UTF_INVALID;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < UTF_SIZ && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ if (!BETWEEN(*u, utfmin[len], utfmax[len]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return len;
+}
+
+static int
+drwl_init(void)
+{
+ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3);
+ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR);
+}
+
+/*
+ * Caller must call drwl_init before drwl_create, and
+ * drwl_destroy on returned context after finalizing usage.
+ */
+static Drwl *
+drwl_create(void)
+{
+ Drwl *drwl;
+
+ if (!(drwl = calloc(1, sizeof(Drwl))))
+ return NULL;
+
+ return drwl;
+}
+
+static void
+drwl_setfont(Drwl *drwl, struct fcft_font *font)
+{
+ if (drwl)
+ drwl->font = font;
+}
+
+/*
+ * Returned font is set within the drawing context if given.
+ * Caller must call drwl_destroy_font on returned font when done using it,
+ * otherwise use drwl_destroy when instead given a drwl context.
+ */
+static struct fcft_font *
+drwl_load_font(Drwl *drwl, size_t fontcount,
+ const char *fonts[static fontcount], const char *attributes)
+{
+ struct fcft_font *font = fcft_from_name(fontcount, fonts, attributes);
+ if (drwl)
+ drwl_setfont(drwl, font);
+ return font;
+}
+
+static void
+drwl_destroy_font(struct fcft_font *font)
+{
+ fcft_destroy(font);
+}
+
+static inline pixman_color_t
+convert_color(uint32_t clr)
+{
+ return (pixman_color_t){
+ ((clr >> 24) & 0xFF) * 0x101,
+ ((clr >> 16) & 0xFF) * 0x101,
+ ((clr >> 8) & 0xFF) * 0x101,
+ (clr & 0xFF) * 0x101
+ };
+}
+
+static void
+drwl_setscheme(Drwl *drwl, uint32_t *scm)
+{
+ if (drwl)
+ drwl->scheme = scm;
+}
+
+static inline int
+drwl_stride(unsigned int width)
+{
+ return (((PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) * width + 7) / 8 + 4 - 1) & -4);
+}
+
+/*
+ * Caller must call drwl_finish_drawing when finished drawing.
+ * Parameter stride can be calculated using drwl_stride.
+ */
+static void
+drwl_prepare_drawing(Drwl *drwl, unsigned int w, unsigned int h,
+ uint32_t *bits, int stride)
+{
+ pixman_region32_t clip;
+
+ if (!drwl)
+ return;
+
+ drwl->pix = pixman_image_create_bits_no_clear(
+ PIXMAN_a8r8g8b8, w, h, bits, stride);
+ pixman_region32_init_rect(&clip, 0, 0, w, h);
+ pixman_image_set_clip_region32(drwl->pix, &clip);
+ pixman_region32_fini(&clip);
+}
+
+static void
+drwl_rect(Drwl *drwl,
+ int x, int y, unsigned int w, unsigned int h,
+ int filled, int invert)
+{
+ pixman_color_t clr;
+ if (!drwl || !drwl->scheme || !drwl->pix)
+ return;
+
+ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]);
+ if (filled)
+ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->pix, &clr, 1,
+ &(pixman_rectangle16_t){x, y, w, h});
+ else
+ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->pix, &clr, 4,
+ (pixman_rectangle16_t[4]){
+ { x, y, w, 1 },
+ { x, y + h - 1, w, 1 },
+ { x, y, 1, h },
+ { x + w - 1, y, 1, h }});
+}
+
+static int
+drwl_text(Drwl *drwl,
+ int x, int y, unsigned int w, unsigned int h,
+ unsigned int lpad, const char *text, int invert)
+{
+ int ty;
+ int utf8charlen, render = x || y || w || h;
+ long x_kern;
+ uint32_t cp = 0, last_cp = 0;
+ pixman_color_t clr;
+ pixman_image_t *fg_pix = NULL;
+ int noellipsis = 0;
+ const struct fcft_glyph *glyph, *eg;
+ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT;
+
+ if (!drwl || (render && (!drwl->scheme || !w || !drwl->pix)) || !text || !drwl->font)
+ return 0;
+
+ if (!render) {
+ w = invert ? invert : ~invert;
+ } else {
+ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]);
+ fg_pix = pixman_image_create_solid_fill(&clr);
+
+ drwl_rect(drwl, x, y, w, h, 1, !invert);
+
+ x += lpad;
+ w -= lpad;
+ }
+
+ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF)
+ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE;
+
+ // U+2026 == …
+ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026, fcft_subpixel_mode);
+
+ while (*text) {
+ utf8charlen = utf8decode(text, &cp);
+
+ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode);
+ if (!glyph)
+ continue;
+
+ x_kern = 0;
+ if (last_cp)
+ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL);
+ last_cp = cp;
+
+ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent;
+
+ /* draw ellipsis if remaining text doesn't fit */
+ if (!noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && *(text + 1) != '\0') {
+ if (drwl_text(drwl, 0, 0, 0, 0, 0, text, 0)
+ - glyph->advance.x < eg->advance.x) {
+ noellipsis = 1;
+ } else {
+ w -= eg->advance.x;
+ pixman_image_composite32(
+ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->pix, 0, 0, 0, 0,
+ x + eg->x, ty - eg->y, eg->width, eg->height);
+ }
+ }
+
+ if ((x_kern + glyph->advance.x) > w)
+ break;
+
+ x += x_kern;
+
+ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)
+ // pre-rendered glyphs (eg. emoji)
+ pixman_image_composite32(
+ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->pix, 0, 0, 0, 0,
+ x + glyph->x, ty - glyph->y, glyph->width, glyph->height);
+ else if (render)
+ pixman_image_composite32(
+ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->pix, 0, 0, 0, 0,
+ x + glyph->x, ty - glyph->y, glyph->width, glyph->height);
+
+ text += utf8charlen;
+ x += glyph->advance.x;
+ w -= glyph->advance.x;
+ }
+
+ if (render)
+ pixman_image_unref(fg_pix);
+
+ return x + (render ? w : 0);
+}
+
+static unsigned int
+drwl_font_getwidth(Drwl *drwl, const char *text)
+{
+ if (!drwl || !drwl->font || !text)
+ return 0;
+ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0);
+}
+
+static unsigned int
+drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n)
+{
+ unsigned int tmp = 0;
+ if (drwl && drwl->font && text && n)
+ tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n);
+ return tmp < n ? tmp : n;
+}
+
+static void
+drwl_finish_drawing(Drwl *drwl)
+{
+ if (drwl && drwl->pix)
+ pixman_image_unref(drwl->pix);
+}
+
+static void
+drwl_destroy(Drwl *drwl)
+{
+ if (drwl->pix)
+ pixman_image_unref(drwl->pix);
+ if (drwl->font)
+ drwl_destroy_font(drwl->font);
+ free(drwl);
+}
+
+static void
+drwl_fini(void)
+{
+ fcft_fini();
+}
diff --git a/mew-run b/mew-run
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
+cache="$cachedir/mew_run"
+
+[ -d "$cachedir" ] || mkdir -p "$cachedir"
+
+uptodate() {
+ [ -f "$cache" ] || return 1
+ IFS=:
+ for path in $PATH; do
+ # non-POSIX
+ test "$path" -nt "$cache" && return 1
+ done
+ return 0
+}
+
+bins() {
+ IFS=:
+ for path in $PATH; do
+ for bin in "$path"/*; do
+ [ -x "$bin" ] && echo "${bin##*/}"
+ done
+ done
+}
+
+path() {
+ if uptodate; then
+ cat "$cache"
+ else
+ bins | sort -u | tee "$cache"
+ fi
+}
+
+path | mew "$@" | ${SHELL:-"/bin/sh"} &
diff --git a/mew.1 b/mew.1
@@ -0,0 +1,152 @@
+.TH EMENU 1 mew\-VERSION
+.SH NAME
+mew \- menu for wayland
+.SH SYNOPSIS
+.B mew
+.RB [ \-biv ]
+.RB [ \-l
+.IR lines ]
+.RB [ \-p
+.IR prompt ]
+.RB [ \-f
+.IR font ]
+.RB [ \-nb
+.IR color ]
+.RB [ \-nf
+.IR color ]
+.RB [ \-sb
+.IR color ]
+.RB [ \-sf
+.IR color ]
+.P
+.BR mew-run " ..."
+.SH DESCRIPTION
+.B mew
+is an simple dynamic menu for Wayland that 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 mew terminates. Entering text will narrow the items to those
+matching the tokens in the input.
+.P
+.B mew-run
+is a script spawned by the compositor that lists programs in the user's $PATH and
+runs the result in their $SHELL.
+.SH OPTIONS
+.TP
+.B \-b
+appear at the bottom of the screen.
+.TP
+.B \-i
+matches menu items case insensitively.
+.TP
+.BI \-l " lines"
+lists item vertically, with the given number of lines.
+.TP
+.BI \-p " prompt"
+defines the prompt to be displayed to the left of the input field.
+.TP
+.BI \-f " font"
+defines the font.
+.TP
+.BI \-nb " color"
+defines the normal background color of the format:
+.IR #RRGGBB[FF]
+.TP
+.BI \-nf " color"
+defines the normal foreground color of the format:
+.IR #RRGGBB[FF]
+.TP
+.BI \-sb " color"
+defines the selected background color of the format:
+.IR #RRGGBB[FF]
+.TP
+.BI \-sf " color"
+defines the selected foreground color of the format:
+.IR #RRGGBB[FF]
+.TP
+.B \-v
+prints version information to stdout, then exits.
+.SH USAGE
+mew is completely controlled by the keyboard. Items are selected using the
+arrow keys, page up, page down, home, and end.
+.TP
+.B Tab
+Copy the selected item to the input field.
+.TP
+.B Return
+Confirm selection. Prints the selected item to stdout and exits, returning
+success.
+.TP
+.B Ctrl-Return
+Confirm selection. Prints the selected item to stdout and continues.
+.TP
+.B Shift\-Return
+Confirm input. Prints the input text to stdout and exits, returning success.
+.TP
+.B Escape
+Exit without selecting an item, returning failure.
+.TP
+.B Ctrl-Left
+Move cursor to the start of the current word
+.TP
+.B Ctrl-Right
+Move cursor to the end of the current word
+.TP
+.B C\-a
+Home
+.TP
+.B C\-b
+Left
+.TP
+.B C\-c
+Escape
+.TP
+.B C\-d
+Delete
+.TP
+.B C\-e
+End
+.TP
+.B C\-f
+Right
+.TP
+.B C\-g
+Escape
+.TP
+.B C\-h
+Backspace
+.TP
+.B C\-i
+Tab
+.TP
+.B C\-j
+Return
+.TP
+.B C\-J
+Shift-Return
+.TP
+.B C\-k
+Delete line right
+.TP
+.B C\-m
+Return
+.TP
+.B C\-M
+Shift-Return
+.TP
+.B C\-n
+Down
+.TP
+.B C\-p
+Up
+.TP
+.B C\-u
+Delete line left
+.TP
+.B C\-w
+Delete word left
+.TP
+.B C\-y
+Paste from Wayland clipboard
+.TP
+.B C\-Y
+Paste from Wayland clipboard
diff --git a/mew.c b/mew.c
@@ -0,0 +1,921 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <locale.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/timerfd.h>
+#include <wayland-client.h>
+#include <xkbcommon/xkbcommon.h>
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+#define LENGTH(X) (sizeof (X) / sizeof (X)[0])
+
+#include "drwl.h"
+#include "poolbuf.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+#define TEXTW(X) (drwl_font_getwidth(drw, (X)) + lrpad)
+
+enum { SchemeNorm, SchemeSel, SchemeOut }; /* color schemes */
+
+struct item {
+ char *text;
+ struct item *left, *right;
+ int out;
+};
+
+static struct {
+ struct wl_keyboard *wl_keyboard;
+ struct xkb_context *xkb_context;
+ struct xkb_keymap *xkb_keymap;
+ struct xkb_state *xkb_state;
+
+ int repeat_delay;
+ int repeat_period;
+ int repeat_timer;
+ enum wl_keyboard_key_state repeat_key_state;
+ xkb_keysym_t repeat_sym;
+
+ int ctrl;
+ int shift;
+} kbd;
+
+static char text[BUFSIZ] = "";
+static int bh, mw, mh;
+static int inputw = 0, promptw;
+static int32_t scale = 1;
+static int lrpad; /* sum of left and right padding */
+static size_t cursor;
+static struct item *items = NULL;
+static struct item *matches, *matchend;
+static struct item *prev, *curr, *next, *sel;
+static int running = 1;
+
+static struct wl_display *display;
+static struct wl_compositor *compositor;
+static struct wl_seat *seat;
+static struct wl_shm *shm;
+static struct wl_data_device_manager *data_device_manager;
+static struct wl_data_device *data_device;
+static struct wl_data_offer *data_offer;
+static struct zwlr_layer_shell_v1 *layer_shell;
+static struct zwlr_layer_surface_v1 *layer_surface;
+static struct wl_surface *surface;
+static struct wl_registry *registry;
+static Drwl *drw;
+
+#include "config.h"
+
+static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+static char *(*fstrstr)(const char *, const char *) = strstr;
+
+static void
+noop()
+{
+ /* Space intentionally left blank */
+}
+
+static void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+parse_color(uint32_t *dest, const char *src)
+{
+ int len;
+
+ if (src[0] == '#')
+ src++;
+ len = strlen(src);
+ if (len != 6 && len != 8)
+ die("bad color: %s", src);
+
+ *dest = strtoul(src, NULL, 16);
+ if (len == 6)
+ *dest = (*dest << 8) | 0xFF;
+}
+
+static void
+loadfonts(void)
+{
+ char fontattrs[12];
+
+ drwl_destroy_font(drw->font);
+ snprintf(fontattrs, sizeof(fontattrs), "dpi=%d", 96 * scale);
+ if (!(drwl_load_font(drw, LENGTH(fonts), fonts, fontattrs)))
+ die("no fonts could be loaded");
+
+ lrpad = drw->font->height;
+ bh = drw->font->height + 2;
+ lines = MAX(lines, 0);
+ mh = (lines + 1) * bh;
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
+}
+
+static unsigned int
+textw_clamp(const char *str, unsigned int n)
+{
+ unsigned int w = drwl_font_getwidth_clamp(drw, str, n) + lrpad;
+ return MIN(w, n);
+}
+
+static void
+appenditem(struct item *item, struct item **list, struct item **last)
+{
+ if (*last)
+ (*last)->right = item;
+ else
+ *list = item;
+
+ item->left = *last;
+ item->right = NULL;
+ *last = item;
+}
+
+static void
+calcoffsets(void)
+{
+ int i, n;
+
+ if (lines > 0)
+ n = lines * bh;
+ else
+ n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
+ /* calculate which items will begin the next page and previous page */
+ for (i = 0, next = curr; next; next = next->right)
+ if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
+ break;
+ for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
+ if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
+ break;
+}
+
+static void
+cleanup(void)
+{
+ size_t i;
+
+ for (i = 0; items && items[i].text; ++i)
+ free(items[i].text);
+ free(items);
+
+ drwl_destroy(drw);
+ drwl_fini();
+
+ wl_data_device_release(data_device);
+ zwlr_layer_shell_v1_destroy(layer_shell);
+ wl_shm_destroy(shm);
+ wl_compositor_destroy(compositor);
+ wl_registry_destroy(registry);
+ wl_display_disconnect(display);
+}
+
+static char *
+cistrstr(const char *h, const char *n)
+{
+ size_t i;
+
+ if (!n[0])
+ return (char *)h;
+
+ for (; *h; ++h) {
+ for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
+ tolower((unsigned char)h[i]); ++i)
+ ;
+ if (n[i] == '\0')
+ return (char *)h;
+ }
+ return NULL;
+}
+
+static int
+drawitem(struct item *item, int x, int y, int w)
+{
+ if (item == sel)
+ drwl_setscheme(drw, colors[SchemeSel]);
+ else if (item->out)
+ drwl_setscheme(drw, colors[SchemeOut]);
+ else
+ drwl_setscheme(drw, colors[SchemeNorm]);
+
+ return drwl_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
+}
+
+static void
+drawmenu(void)
+{
+ unsigned int curpos;
+ struct item *item;
+ int x = 0, y = 0, w;
+ PoolBuf *buf;
+
+ if (!(buf = poolbuf_create(shm, mw, mh)))
+ die("poolbuf_create:");
+
+ drwl_prepare_drawing(drw, mw, mh, buf->data, buf->stride);
+
+ drwl_setscheme(drw, colors[SchemeNorm]);
+ drwl_rect(drw, 0, 0, mw, mh, 1, 1);
+
+ if (prompt && *prompt) {
+ drwl_setscheme(drw, colors[SchemeSel]);
+ x = drwl_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
+ }
+ /* draw input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+ drwl_setscheme(drw, colors[SchemeNorm]);
+ drwl_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+
+ curpos = TEXTW(text) - TEXTW(&text[cursor]);
+ if ((curpos += lrpad / 2 - 1) < w) {
+ drwl_setscheme(drw, colors[SchemeNorm]);
+ drwl_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+ }
+
+ if (lines > 0) {
+ /* draw vertical list */
+ for (item = curr; item != next; item = item->right)
+ drawitem(item, x, y += bh, mw - x);
+ } else if (matches) {
+ /* draw horizontal list */
+ x += inputw;
+ w = TEXTW("<");
+ if (curr->left) {
+ drwl_setscheme(drw, colors[SchemeNorm]);
+ drwl_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
+ }
+ x += w;
+ for (item = curr; item != next; item = item->right)
+ x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">")));
+ if (next) {
+ w = TEXTW(">");
+ drwl_setscheme(drw, colors[SchemeNorm]);
+ drwl_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
+ }
+ }
+
+ drwl_finish_drawing(drw);
+ wl_surface_set_buffer_scale(surface, scale);
+ wl_surface_attach(surface, buf->wl_buf, 0, 0);
+ wl_surface_damage_buffer(surface, 0, 0, mw, mh);
+ poolbuf_destroy(buf);
+ wl_surface_commit(surface);
+}
+
+static void
+match(void)
+{
+ static char **tokv = NULL;
+ static int tokn = 0;
+
+ char buf[sizeof text], *s;
+ int i, tokc = 0;
+ size_t len, textsize;
+ struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
+
+ strcpy(buf, text);
+ /* separate input text into tokens to be matched individually */
+ for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
+ if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
+ die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
+ len = tokc ? strlen(tokv[0]) : 0;
+
+ matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
+ textsize = strlen(text) + 1;
+ for (item = items; item && item->text; item++) {
+ for (i = 0; i < tokc; i++)
+ if (!fstrstr(item->text, tokv[i]))
+ break;
+ if (i != tokc) /* not all tokens match */
+ continue;
+ /* exact matches go first, then prefixes, then substrings */
+ if (!tokc || !fstrncmp(text, item->text, textsize))
+ appenditem(item, &matches, &matchend);
+ else if (!fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lprefix, &prefixend);
+ else
+ appenditem(item, &lsubstr, &substrend);
+ }
+ if (lprefix) {
+ if (matches) {
+ matchend->right = lprefix;
+ lprefix->left = matchend;
+ } else
+ matches = lprefix;
+ matchend = prefixend;
+ }
+ if (lsubstr) {
+ if (matches) {
+ matchend->right = lsubstr;
+ lsubstr->left = matchend;
+ } else
+ matches = lsubstr;
+ matchend = substrend;
+ }
+ curr = sel = matches;
+ calcoffsets();
+}
+
+static void
+insert(const char *str, ssize_t n)
+{
+ if (strlen(text) + n > sizeof text - 1)
+ return;
+ /* move existing text out of the way, insert new text, and update cursor */
+ memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
+ if (n > 0)
+ memcpy(&text[cursor], str, n);
+ cursor += n;
+ match();
+}
+
+static size_t
+nextrune(int inc)
+{
+ ssize_t n;
+
+ /* return location of next utf8 rune in the given direction (+1 or -1) */
+ for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
+ ;
+ return n;
+}
+
+static void
+movewordedge(int dir)
+{
+ if (dir < 0) { /* move cursor to the start of the word*/
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ } else { /* move cursor to the end of the word */
+ while (text[cursor] && strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ while (text[cursor] && !strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ }
+}
+
+static void
+paste(void)
+{
+ int fds[2];
+ ssize_t n;
+ char buf[1024];
+
+ if (!data_offer)
+ return;
+
+ if (pipe(fds) < 0)
+ die("pipe");
+
+ wl_data_offer_receive(data_offer, "text/plain", fds[1]);
+ close(fds[1]);
+
+ wl_display_roundtrip(display);
+
+ for (;;) {
+ n = read(fds[0], buf, sizeof(buf));
+ if (n <= 0)
+ break;
+ insert(buf, n);
+ }
+ close(fds[0]);
+
+ wl_data_offer_destroy(data_offer);
+}
+
+static void
+keyboard_keypress(enum wl_keyboard_key_state state, xkb_keysym_t sym, int ctrl, int shift)
+{
+ char buf[8];
+
+ if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+ return;
+
+ if (ctrl) {
+ switch (xkb_keysym_to_lower(sym)) {
+ case XKB_KEY_a: sym = XKB_KEY_Home; break;
+ case XKB_KEY_b: sym = XKB_KEY_Left; break;
+ case XKB_KEY_c: sym = XKB_KEY_Escape; break;
+ case XKB_KEY_d: sym = XKB_KEY_Delete; break;
+ case XKB_KEY_e: sym = XKB_KEY_End; break;
+ case XKB_KEY_f: sym = XKB_KEY_BackSpace; break;
+ case XKB_KEY_g: sym = XKB_KEY_Escape; break;
+ case XKB_KEY_h: sym = XKB_KEY_BackSpace; break;
+ case XKB_KEY_i: sym = XKB_KEY_Tab; break;
+ case XKB_KEY_j: /* fallthrough */
+ case XKB_KEY_J: /* fallthrough */
+ case XKB_KEY_m: /* fallthrough */
+ case XKB_KEY_M: sym = XKB_KEY_Return; break;
+ case XKB_KEY_n: sym = XKB_KEY_Right; break;
+ case XKB_KEY_p: sym = XKB_KEY_Up; break;
+
+ case XKB_KEY_k: /* delete right */
+ text[cursor] = '\0';
+ match();
+ goto draw;
+ case XKB_KEY_u: /* delete left */
+ insert(NULL, 0 - cursor);
+ goto draw;
+ case XKB_KEY_w: /* delete word */
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ insert(NULL, nextrune(-1) - cursor);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ insert(NULL, nextrune(-1) - cursor);
+ goto draw;
+ case XKB_KEY_y: /* paste selection */
+ case XKB_KEY_Y:
+ paste();
+ goto draw;
+ case XKB_KEY_Left:
+ case XKB_KEY_KP_Left:
+ movewordedge(-1);
+ goto draw;
+ case XKB_KEY_Right:
+ case XKB_KEY_KP_Right:
+ movewordedge(+1);
+ goto draw;
+ case XKB_KEY_Return:
+ case XKB_KEY_KP_Enter:
+ break;
+ case XKB_KEY_bracketleft:
+ cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ switch (sym) {
+ case XKB_KEY_Delete:
+ case XKB_KEY_KP_Delete:
+ if (text[cursor] == '\0')
+ return;
+ cursor = nextrune(+1);
+ /* fallthrough */
+ case XKB_KEY_BackSpace:
+ if (cursor == 0)
+ return;
+ insert(NULL, nextrune(-1) - cursor);
+ break;
+ case XKB_KEY_End:
+ case XKB_KEY_KP_End:
+ if (text[cursor] != '\0') {
+ cursor = strlen(text);
+ break;
+ }
+ if (next) {
+ /* jump to end of list and position items in reverse */
+ curr = matchend;
+ calcoffsets();
+ curr = prev;
+ calcoffsets();
+ while (next && (curr = curr->right))
+ calcoffsets();
+ }
+ sel = matchend;
+ break;
+ case XKB_KEY_Escape:
+ cleanup();
+ exit(EXIT_FAILURE);
+ case XKB_KEY_Home:
+ case XKB_KEY_KP_Home:
+ if (sel == matches) {
+ cursor = 0;
+ break;
+ }
+ sel = curr = matches;
+ calcoffsets();
+ break;
+ case XKB_KEY_Left:
+ case XKB_KEY_KP_Left:
+ if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
+ cursor = nextrune(-1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XKB_KEY_Up:
+ case XKB_KEY_KP_Up:
+ if (sel && sel->left && (sel = sel->left)->right == curr) {
+ curr = prev;
+ calcoffsets();
+ }
+ break;
+ case XKB_KEY_Next:
+ case XKB_KEY_KP_Next:
+ if (!next)
+ return;
+ sel = curr = next;
+ calcoffsets();
+ break;
+ case XKB_KEY_Prior:
+ case XKB_KEY_KP_Prior:
+ if (!prev)
+ return;
+ sel = curr = prev;
+ calcoffsets();
+ break;
+ case XKB_KEY_Return:
+ case XKB_KEY_KP_Enter:
+ puts((sel && !shift) ? sel->text : text);
+ if (!ctrl)
+ running = 0;
+ return;
+ case XKB_KEY_Right:
+ case XKB_KEY_KP_Right:
+ if (text[cursor] != '\0') {
+ cursor = nextrune(+1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XKB_KEY_Down:
+ case XKB_KEY_KP_Down:
+ if (sel && sel->right && (sel = sel->right) == next) {
+ curr = next;
+ calcoffsets();
+ }
+ break;
+ case XKB_KEY_Tab:
+ if (!sel)
+ return;
+ cursor = strnlen(sel->text, sizeof text - 1);
+ memcpy(text, sel->text, cursor);
+ text[cursor] = '\0';
+ match();
+ break;
+ default:
+ if (xkb_keysym_to_utf8(sym, buf, 8))
+ insert(buf, strnlen(buf, 8));
+ }
+draw:
+ drawmenu();
+}
+
+static void
+keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size)
+{
+ char *map_shm;
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
+ die("unknown keymap");
+
+ map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (map_shm == MAP_FAILED)
+ die("mmap:");
+
+ kbd.xkb_keymap = xkb_keymap_new_from_string(kbd.xkb_context, map_shm,
+ XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ munmap(map_shm, size);
+ close(fd);
+
+ kbd.xkb_state = xkb_state_new(kbd.xkb_keymap);
+}
+
+static void
+keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state)
+{
+ struct itimerspec spec = { 0 };
+ enum wl_keyboard_key_state key_state = _key_state;
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(kbd.xkb_state, key + 8);
+
+ keyboard_keypress(key_state, sym, kbd.ctrl, kbd.shift);
+
+ if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && kbd.repeat_period >= 0) {
+ kbd.repeat_key_state = key_state;
+ kbd.repeat_sym = sym;
+ spec.it_value.tv_sec = kbd.repeat_delay / 1000;
+ spec.it_value.tv_nsec = (kbd.repeat_delay % 1000) * 1000000l;
+ }
+ timerfd_settime(kbd.repeat_timer, 0, &spec, NULL);
+}
+
+static void
+keyboard_handle_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)
+{
+ xkb_state_update_mask(kbd.xkb_state,
+ mods_depressed, mods_latched, mods_locked, 0, 0, group);
+ kbd.ctrl = xkb_state_mod_name_is_active(kbd.xkb_state,
+ XKB_MOD_NAME_CTRL,
+ XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
+ kbd.shift = xkb_state_mod_name_is_active(kbd.xkb_state,
+ XKB_MOD_NAME_SHIFT,
+ XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
+}
+
+static void
+keyboard_repeat(void)
+{
+ struct itimerspec spec = { 0 };
+
+ keyboard_keypress(kbd.repeat_key_state, kbd.repeat_sym, kbd.ctrl, kbd.shift);
+
+ spec.it_value.tv_sec = kbd.repeat_period / 1000;
+ spec.it_value.tv_nsec = (kbd.repeat_period % 1000) * 1000000l;
+ timerfd_settime(kbd.repeat_timer, 0, &spec, NULL);
+}
+
+static void
+keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay)
+{
+ kbd.repeat_delay = delay;
+ kbd.repeat_period = rate >= 0 ? 1000 / rate : -1;
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ .keymap = keyboard_handle_keymap,
+ .enter = noop,
+ .leave = noop,
+ .key = keyboard_handle_key,
+ .modifiers = keyboard_handle_modifiers,
+ .repeat_info = keyboard_handle_repeat_info,
+};
+
+static void
+layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *surface,
+ uint32_t serial, uint32_t width, uint32_t height)
+{
+ mw = width * scale;
+ mh = height * scale;
+ inputw = mw / 3; /* input width: ~33% of monitor width */
+ zwlr_layer_surface_v1_ack_configure(surface, serial);
+ drawmenu();
+}
+
+static void
+layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *surface)
+{
+ running = 0;
+}
+
+static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
+ .configure = layer_surface_handle_configure,
+ .closed = layer_surface_handle_closed,
+};
+
+static void
+surface_handle_preferred_scale(void *data,
+ struct wl_surface *wl_surface, int32_t factor)
+{
+ scale = factor;
+ loadfonts();
+ zwlr_layer_surface_v1_set_size(layer_surface, 0, mh / scale);
+}
+
+static const struct wl_surface_listener surface_listener = {
+ .enter = noop,
+ .leave = noop,
+ .preferred_buffer_scale = surface_handle_preferred_scale,
+ .preferred_buffer_transform = noop,
+};
+
+static void
+data_device_handle_selection(void *data, struct wl_data_device *data_device,
+ struct wl_data_offer *_data_offer)
+{
+ data_offer = _data_offer;
+}
+
+static const struct wl_data_device_listener data_device_listener = {
+ .data_offer = noop,
+ .selection = data_device_handle_selection,
+};
+
+static void
+seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps)
+{
+ if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD))
+ return;
+
+ kbd.wl_keyboard = wl_seat_get_keyboard(seat);
+ if (!(kbd.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
+ die("xkb_context_new failed");
+ if ((kbd.repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0)) < 0)
+ die("timerfd_create:");
+ wl_keyboard_add_listener(kbd.wl_keyboard, &keyboard_listener, NULL);
+}
+
+static const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_handle_capabilities,
+ .name = noop,
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version)
+{
+ if (!strcmp(interface, wl_compositor_interface.name))
+ compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6);
+ else if (!strcmp(interface, wl_shm_interface.name))
+ shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+ else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name))
+ layer_shell = wl_registry_bind(registry, name,
+ &zwlr_layer_shell_v1_interface, 1);
+ else if (!strcmp(interface, wl_data_device_manager_interface.name))
+ data_device_manager = wl_registry_bind(registry, name,
+ &wl_data_device_manager_interface, 3);
+ else if (!strcmp(interface, wl_seat_interface.name)) {
+ seat = wl_registry_bind (registry, name, &wl_seat_interface, 4);
+ wl_seat_add_listener(seat, &seat_listener, NULL);
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_global,
+ .global_remove = noop,
+};
+
+static void
+readstdin(void)
+{
+ char *line = NULL;
+ size_t i, itemsiz = 0, linesiz = 0;
+ ssize_t len;
+
+ /* read each line from stdin and add it to the item list */
+ for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
+ if (i + 1 >= itemsiz) {
+ itemsiz += 256;
+ if (!(items = realloc(items, itemsiz * sizeof(*items))))
+ die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
+ }
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ if (!(items[i].text = strdup(line)))
+ die("strdup:");
+
+ items[i].out = 0;
+ }
+ free(line);
+ if (items)
+ items[i].text = NULL;
+ lines = MIN(lines, i);
+}
+
+static void
+run(void)
+{
+ struct pollfd pfds[] = {
+ { wl_display_get_fd(display), POLLIN },
+ { kbd.repeat_timer, POLLIN },
+ };
+
+ match();
+ drawmenu();
+
+ while (running) {
+ if (wl_display_prepare_read(display) < 0)
+ if (wl_display_dispatch_pending(display) < 0)
+ die("wl_display_dispatch_pending:");
+
+ if (wl_display_flush(display) < 0)
+ die("wl_display_flush:");
+
+ if (poll(pfds, LENGTH(pfds), -1) < 0) {
+ wl_display_cancel_read(display);
+ die("poll:");
+ }
+
+ if (pfds[1].revents & POLLIN)
+ keyboard_repeat();
+
+ if (!(pfds[0].revents & POLLIN)) {
+ wl_display_cancel_read(display);
+ continue;
+ }
+
+ if (wl_display_read_events(display) < 0)
+ die("wl_display_read_events:");
+ if (wl_display_dispatch_pending(display) < 0)
+ die("wl_display_dispatch_pending:");
+ }
+}
+
+static void
+setup(void)
+{
+ if (!(display = wl_display_connect(NULL)))
+ die("failed to connect to wayland");
+
+ registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, ®istry_listener, NULL);
+ wl_display_roundtrip(display);
+
+ if (!compositor)
+ die("wl_compositor not available");
+ if (!shm)
+ die("wl_shm not available");
+ if (!layer_shell)
+ die("layer_shell not available");
+ if (!data_device_manager)
+ die("data_device_manager not available");
+
+ data_device = wl_data_device_manager_get_data_device(
+ data_device_manager, seat);
+ wl_data_device_add_listener(data_device, &data_device_listener, NULL);
+
+ drwl_init();
+ if (!(drw = drwl_create()))
+ die("cannot create drwl drawing context");
+ loadfonts();
+
+ match();
+
+ surface = wl_compositor_create_surface(compositor);
+ wl_surface_add_listener(surface, &surface_listener, NULL);
+
+ layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell,
+ surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "mew");
+ zwlr_layer_surface_v1_set_size(layer_surface, 0, mh);
+ zwlr_layer_surface_v1_set_anchor(layer_surface,
+ (top ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP : ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ) |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
+ 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, NULL);
+
+ wl_surface_commit(surface);
+ wl_display_roundtrip(display);
+}
+
+static void
+usage(void)
+{
+ die("usage: mew [-biv] [-l lines] [-p prompt] [-f font]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color]");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ /* these options take no arguments */
+ if (!strcmp(argv[i], "-v")) {
+ puts("mew-"VERSION);
+ exit(0);
+ } else if (!strcmp(argv[i], "-b"))
+ top = 0;
+ else if (!strcmp(argv[i], "-i")) {
+ fstrncmp = strncasecmp;
+ fstrstr = cistrstr;
+ } else if (i + 1 == argc)
+ usage();
+ else if (!strcmp(argv[i], "-l"))
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-p"))
+ prompt = argv[++i];
+ else if (!strcmp(argv[i], "-f"))
+ fonts[0] = argv[++i];
+ else if (!strcmp(argv[i], "-nb"))
+ parse_color(&colors[SchemeNorm][ColBg], argv[++i]);
+ else if (!strcmp(argv[i], "-nf"))
+ parse_color(&colors[SchemeNorm][ColFg], argv[++i]);
+ else if (!strcmp(argv[i], "-sb"))
+ parse_color(&colors[SchemeSel][ColBg], argv[++i]);
+ else if (!strcmp(argv[i], "-sf"))
+ parse_color(&colors[SchemeSel][ColFg], argv[++i]);
+ else
+ usage();
+ }
+
+ readstdin();
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath", NULL) == -1)
+ die("pledge");
+#endif
+ setup();
+ run();
+ cleanup();
+
+ return EXIT_SUCCESS;
+}
diff --git a/poolbuf.h b/poolbuf.h
@@ -0,0 +1,112 @@
+/*
+ * poolbuf - https://codeberg.org/sewn/drwl
+ * See LICENSE file for copyright and license details.
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <wayland-client.h>
+
+#include "drwl.h"
+
+typedef struct {
+ struct wl_buffer *wl_buf;
+ int32_t stride;
+ int32_t size;
+ void *data;
+} PoolBuf;
+
+#if defined(__linux__) || defined(___FreeBSD__)
+#ifdef __linux__
+#include <linux/memfd.h>
+#endif
+#define __POOLBUF_HAS_MEMFD_CREATE
+int memfd_create(const char *, unsigned);
+#else
+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
+create_shm(void)
+{
+ char name[] = "/drwl-XXXXXX";
+ int retries = 100;
+
+ do {
+ randname(name + sizeof(name) - 7);
+ --retries;
+ 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;
+}
+#endif
+
+/* Caller must call poolbuf_destroy after finalizing usage */
+static PoolBuf *
+poolbuf_create(struct wl_shm *shm, int32_t width, int32_t height)
+{
+ int fd;
+ void *data;
+ struct wl_shm_pool *shm_pool;
+ struct wl_buffer *wl_buf;
+ int32_t stride = drwl_stride(width);
+ int32_t size = stride * height;
+ PoolBuf *buf;
+
+#ifdef __POOLBUF_HAS_MEMFD_CREATE
+ fd = memfd_create("drwl-shm-buffer-pool",
+ MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL);
+#else
+ fd = create_shm();
+#endif
+ if (fd < 0)
+ return NULL;
+
+ if ((ftruncate(fd, size)) < 0) {
+ close(fd);
+ return NULL;
+ }
+
+ data = mmap(NULL, size,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ close(fd);
+ return NULL;
+ }
+
+ shm_pool = wl_shm_create_pool(shm, fd, size);
+ wl_buf = wl_shm_pool_create_buffer(shm_pool, 0,
+ width, height, stride, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(shm_pool);
+ close(fd);
+
+ buf = calloc(1, sizeof(PoolBuf));
+ buf->wl_buf = wl_buf;
+ buf->stride = stride;
+ buf->size = size;
+ buf->data = data;
+ return buf;
+}
+
+static void
+poolbuf_destroy(PoolBuf *buf)
+{
+ wl_buffer_destroy(buf->wl_buf);
+ munmap(buf->data, buf->size);
+ free(buf);
+}
diff --git a/wlr-layer-shell-unstable-v1.xml b/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>