From 86f1a1f51b0b8a03c886ca205e8c985abafb0d2f Mon Sep 17 00:00:00 2001 From: kvj Date: Tue, 24 Oct 2023 12:48:06 +0200 Subject: [PATCH] Initial commit --- LICENSE | 30 ++ Makefile | 64 ++++ PKGBUILD | 40 +++ README.md | 30 ++ arg.h | 49 +++ config.def.h | 33 ++ config.h | 33 ++ config.mk | 31 ++ dmenu | Bin 0 -> 43464 bytes dmenu.1 | 224 +++++++++++++ dmenu.c | 924 +++++++++++++++++++++++++++++++++++++++++++++++++++ dmenu.o | Bin 0 -> 38952 bytes dmenu_path | 13 + dmenu_run | 2 + drw.c | 435 ++++++++++++++++++++++++ drw.h | 57 ++++ drw.o | Bin 0 -> 10520 bytes stest | Bin 0 -> 16480 bytes stest.1 | 90 +++++ stest.c | 109 ++++++ stest.o | Bin 0 -> 5312 bytes util.c | 35 ++ util.h | 8 + util.o | Bin 0 -> 2256 bytes 24 files changed, 2207 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 PKGBUILD create mode 100644 README.md create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.h create mode 100644 config.mk create mode 100755 dmenu create mode 100644 dmenu.1 create mode 100644 dmenu.c create mode 100644 dmenu.o create mode 100755 dmenu_path create mode 100755 dmenu_run create mode 100644 drw.c create mode 100644 drw.h create mode 100644 drw.o create mode 100755 stest create mode 100644 stest.1 create mode 100644 stest.c create mode 100644 stest.o create mode 100644 util.c create mode 100644 util.h create mode 100644 util.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9762166 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2019 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +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 new file mode 100644 index 0000000..a03a95c --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: options dmenu stest + +options: + @echo dmenu build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all options clean dist install uninstall diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..3724f88 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,40 @@ +# Maintainer: Zurab Benashvili + +_pkgname=dmenu +pkgname=$_pkgname +pkgver=4.9 +pkgrel=1 +epoch=1 +pkgdesc='Generic menu for X' +url='https://tools.suckless.org/dmenu/' +arch=('i686' 'x86_64') +license=('MIT') +makedepends=('git') +depends=('sh' 'libxinerama' 'libxft' 'freetype2') +provides=($_pkgname) +conflicts=($_pkgname) +source=("git+https://github.com/binier/${_pkgname}.git") +sha256sums=('SKIP') + +prepare() { + cd $_pkgname + # to use a custom config.h, place it in the package directory + if [[ -f ${SRCDEST}/config.h ]]; then + cp "${SRCDEST}/config.h" . + fi +} + +build(){ + cd $_pkgname + make \ + X11INC=/usr/include/X11 \ + X11LIB=/usr/lib/X11 \ + FREETYPEINC=/usr/include/freetype2 +} + +package() { + cd $_pkgname + make PREFIX=/usr DESTDIR="$pkgdir" install + install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE +} + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b175d2 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# dmenu - dynamic menu +dmenu is an efficient dynamic menu for X. + +## Requirements +In order to build dmenu you need the Xlib header files. + +## Installation +Edit config.mk to match your local setup (dmenu is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +## Running dmenu +See the man page for details. + +## Patches +- dmenu-center-20200111-8cd37e1.diff +- dmenu-lineheight-4.9.diff +- dmenu-password-4.7.diff +- dmenu-rejectnomatch-4.7.diff +- dmenu-preselect-20200513-db6093f.diff +- dmenu-initialtext-4.7.diff +- dmenu-highlight-4.9.diff +- dmenu-incremental-20160702-3c91eed.diff +- dmenu-dynamicoptions-20200526-01e2dfc7.diff + diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..6571821 --- /dev/null +++ b/config.def.h @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int centered = 0; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeSelHighlight] = { "#ffc978", "#005577" }, + [SchemeNormHighlight] = { "#ffc978", "#222222" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; +static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* -n option; preselected item starting from 0 */ +static unsigned int preselected = 0; diff --git a/config.h b/config.h new file mode 100644 index 0000000..8f64685 --- /dev/null +++ b/config.h @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int centered = 0; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "Noto-10.5" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#010101" }, + [SchemeSel] = { "#010101", "#bbbbbb" }, + [SchemeSelHighlight] = { "#dfdfdf", "#010101" }, + [SchemeNormHighlight] = { "#bbbbbb", "#010101" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; +static unsigned int lineheight = 37; /* -h option; minimum height of a menu line */ + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* -n option; preselected item starting from 0 */ +static unsigned int preselected = 0; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..0929b4a --- /dev/null +++ b/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 4.9 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu b/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..e809d920fb2cc634483da7ede9c9d68e1441a28c GIT binary patch literal 43464 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 literal 0 HcmV?d00001 diff --git a/dmenu.1 b/dmenu.1 new file mode 100644 index 0000000..efae9e0 --- /dev/null +++ b/dmenu.1 @@ -0,0 +1,224 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiIPrv ] +.RB [ \-l +.IR lines ] +.RB [ \-n +.IR number ] +.RB [ \-it +.IR text ] +.RB [ \-dy +.IR command ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, 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 dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-c +dmenu appears centered on the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.B \-I +dmenu outputs text each time a key is pressed. +.TP +.B \-P +dmenu will not directly display the keyboard input, but instead replace it with dots. All data from stdin will be ignored. +.TP +.B \-r +dmenu will reject any input which would result in no matching option left. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-h " height" +dmenu uses a menu line of at least 'height' pixels tall, but no less than 8. +.TP +.BI \-n " number" +preseslected item starting from 0. +.TP +.BI \-it " text". +start with initial text input. +.TP +.BI \-dy " command". +run command on every keypress and set dmenu options to it's output. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu 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 primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu.c b/dmenu.c new file mode 100644 index 0000000..c44cc3b --- /dev/null +++ b/dmenu.c @@ -0,0 +1,924 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw, passwd = 0; +static int lrpad; /* sum of left and right padding */ +static int reject_no_match = 0; +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +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 : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + return NULL; +} + +static void +drawhighlights(struct item *item, int x, int y, int maxw) +{ + char restorechar, tokens[sizeof text], *highlight, *token; + int indentx, highlightlen; + + drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); + strcpy(tokens, text); + for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { + highlight = fstrstr(item->text, token); + while (highlight) { + // Move item str end, calc width for highlight indent, & restore + highlightlen = highlight - item->text; + restorechar = *highlight; + item->text[highlightlen] = '\0'; + indentx = TEXTW(item->text); + item->text[highlightlen] = restorechar; + + // Move highlight str end, draw highlight, & restore + restorechar = highlight[strlen(token)]; + highlight[strlen(token)] = '\0'; + drw_text( + drw, + x + indentx - (lrpad / 2) - 1, + y, + MIN(maxw - indentx, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + ); + highlight[strlen(token)] = restorechar; + + if (strlen(highlight) - strlen(token) < strlen(token)) break; + highlight = fstrstr(highlight + strlen(token), token); + } + } +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + int r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + drawhighlights(item, x, y, w); + return r; +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, fh = drw->fonts->h, w; + char *censort; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + if (passwd) { + censort = ecalloc(1, sizeof(text)); + memset(censort, '*', strlen(text)); + drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); + free(censort); + } else drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 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) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void readstdin(FILE* stream); + +static void +refreshoptions(){ + int dynlen = strlen(dynamic); + char* cmd= malloc(dynlen + strlen(text)+2); + if(cmd == NULL) + die("malloc:"); + sprintf(cmd,"%s %s",dynamic, text); + FILE *stream = popen(cmd, "r"); + if(!stream) + die("popen(%s):",cmd); + readstdin(stream); + int pc = pclose(stream); + if(pc == -1) + die("pclose:"); + free(cmd); + curr = sel = items; +} + +static void +match(void) +{ + if(dynamic && *dynamic){ + refreshoptions(); + } + + 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 %u 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 && !(dynamic && *dynamic)) /* 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; + + static char last[BUFSIZ] = ""; + if(reject_no_match) { + /* store last text value in case we need to revert it */ + memcpy(last, text, BUFSIZ); + } + + /* 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(); + + if(!matches && reject_no_match) { + /* revert to last text value if theres no match */ + memcpy(text, last, BUFSIZ); + 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 +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_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); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + movewordedge(-1); + goto draw; + case XK_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl(*buf)) + insert(buf, len); + break; + case XK_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_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 XK_Escape: + cleanup(); + exit(1); + case XK_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + if (incremental) { + puts(text); + fflush(stdout); + } + drawmenu(); +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(FILE* stream) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + if (passwd) { + inputw = lines = 0; + return; + } + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stream); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + if (!dynamic || !*dynamic) + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + int i; + + while (!XNextEvent(dpy, &ev)) { + if (preselected) { + for (i = 0; i < preselected; i++) { + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + } + drawmenu(); + preselected = 0; + } + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + } + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + fputs("usage: dmenu [-bfiIPrv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-h height] [-n number] [-it text] [-dy command]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } + else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ + centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } + else if (!strcmp(argv[i], "-I")) /* incremental */ + incremental = 1; + else if (!strcmp(argv[i], "-P")) /* is the input a password */ + passwd = 1; + else if (!strcmp(argv[i], "-r")) /* reject input which results in no match */ + reject_no_match = 1; + else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */ + } + else if (!strcmp(argv[i], "-n")) /* preselected item index */ + preselected = atoi(argv[++i]); + else if (!strcmp(argv[i], "-it")) { /* initial text */ + const char * text = argv[++i]; + insert(text, strlen(text)); + } + else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ + dynamic = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + if(!(dynamic && *dynamic)) + readstdin(stdin); + } else { + if(!(dynamic && *dynamic)) + readstdin(stdin); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu.o b/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..46ec629270f2a33c2c8b3af3940509142acabbe8 GIT binary patch literal 38952 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 NJk1&DNY&|~{|99T3i$v4 literal 0 HcmV?d00001 diff --git a/dmenu_path b/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu_run b/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/drw.c b/drw.c new file mode 100644 index 0000000..8fd1ca4 --- /dev/null +++ b/drw.c @@ -0,0 +1,435 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#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 long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +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 size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/drw.h b/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/drw.o b/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..08a05a2cbfae8af203bf6dcdd6322c8b08e99d8b GIT binary patch literal 10520 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 literal 0 HcmV?d00001 diff --git a/stest b/stest new file mode 100755 index 0000000000000000000000000000000000000000..8976ebf4c8518392ac5a98d0d12463e40d7719b4 GIT binary patch literal 16480 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