Compare commits

...

26 Commits

Author SHA1 Message Date
hazen2215 7489a6da57 config.h: modify worddelimiters 2023-08-13 00:33:46 +09:00
hazen2215 d3f7be8978 add xim support 2023-08-13 00:33:46 +09:00
hazen2215 35d070b093 add preselect patch 2023-08-13 00:33:46 +09:00
hazen2215 ca390636ed Makefile, config.mk: respect user {C,CPP,LD}FLAGS 2023-08-13 00:33:46 +09:00
hazen2215 f48b9a3781 update color 2023-08-13 00:33:46 +09:00
hazen2215 51628af2bc make ^U delete whole line 2023-08-13 00:33:46 +09:00
hazen2215 1089b2f4de add config.h 2023-08-13 00:33:46 +09:00
NRK 7ab0cb5ef0 drw: minor improvement to the nomatches cache
1. use `unsigned int` to store the codepoints, this avoids waste on
   common case where `long` is 64bits. and POSIX guarantees `int` to be
   at least 32bits so there's no risk of truncation.
2. since switching to `unsigned int` cuts down the memory requirement by
   half, double the cache size from 64 to 128.
3. instead of a linear search, use a simple hash-table for O(1) lookups.
2023-07-07 15:03:57 +02:00
Lucas de Sena 0fe460dbd4 fix BadMatch error when embedding on some windows
When embedded into another window, dmenu will fail with the BadMatch
error if that window have not the same colormap/depth/visual as the
root window.

That happens because dmenu inherits the colormap/depth/visual from
its parent, but draws on a pixmap created based on the root window
using a GC created for the root window (see drw.c).  A BadMatch will
occur when copying the content of the pixmap into dmenu's window.

A solution is to create dmenu's window inside root and then reparent
it if embeded.

See this mail[1] on ports@openbsd.org mailing list for context.

[1]: https://marc.info/?l=openbsd-ports&m=168072150814664&w=2
2023-04-06 20:28:56 +02:00
Hiltjo Posthuma dfbbf7f6e1 readstdin: reduce memory-usage by duplicating the line from getline()
Improves upon commit 32db2b1251

The getline() implementation often uses a more greedy way of allocating memory.
Using this buffer directly and forcing an allocation (by setting it to NULL)
would waste a bit of extra space, depending on the implementation of course.

Tested on musl libc and glibc.
The current glibc version allocates a minimum of 120 bytes per line.
For smaller lines musl libc seems less wasteful but still wastes a few bytes
per line.

On a dmenu_path listing on my system the memory usage was about 350kb (old) vs
30kb (new) on Void Linux glibc.

Side-note that getline() also reads NUL bytes in lines, while strdup() would
read until the NUL byte. Since dmenu reads text lines either is probably
fine(tm). Also rename junk to linesiz.
2023-03-08 21:28:51 +01:00
Hiltjo Posthuma ba1a347dca readstdin: allocate amount of items
Keep track of the amount of items (not a total buffer size), allocate an array of
new items. For now change BUFSIZ bytes to 256 * sizeof(struct item)).
2022-10-31 11:52:30 +01:00
Hiltjo Posthuma bcbc1ef5c4 readstdin: add a comment
Maybe too obvious / redundant, but OK.
2022-10-31 11:46:10 +01:00
NRK 689d9bfcf6 fix leak when getline fails
according to the getline(3) documentation, the calling code needs to
free the buffer even if getline fails.

dmenu currently doesn't do that which results in a small leak in case of
failure (e.g when piped /dev/null)

	$ ./dmenu < /dev/null
	==8201==ERROR: LeakSanitizer: detected memory leaks
	Direct leak of 120 byte(s) in 1 object(s) allocated from:
	    #0 0x7f6bf5785ef7 in malloc
	    #1 0x7f6bf538ec84 in __getdelim
	    #2 0x405d0c in readstdin dmenu.c:557

moving `line = NULL` inside the loop body wasn't strictly necessary, but
IMO it makes it more apparent that `line` is getting cleared to NULL
after each successful iteration.
2022-10-31 11:40:35 +01:00
Hiltjo Posthuma e42c036634 dmenu: small XmbLookupString code improvements
* Increase the length of composed strings to the same limit as st (32 to 64 bytes).
* Initialize ksym to NoSymbol to be safe: currently this is not an issue though.
* Add comments to clarify the return values of XmbLookupString a bit.
2022-10-26 09:43:17 +02:00
Hiltjo Posthuma 1d2b462acf bump version to 5.2 2022-10-04 19:36:02 +02:00
Tom Schwindl 7ec32fe494 dmenu: use die() to print the usage message 2022-10-01 13:20:40 +02:00
Hiltjo Posthuma fce06f437d remove workaround for a crash with color emojis on some systems, now fixed in libXft 2.3.5
https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS
2022-09-17 15:32:26 +02:00
Hiltjo Posthuma 1e8c5b68f4 fix a regression in the previous commit for tab complete
Reported by Santtu Lakkala <inz@inz.fi>, thanks!
2022-09-02 19:09:50 +02:00
NRK 528d39b011 tab-complete: figure out the size before copying
we already need to know the string length since `cursor` needs to be
adjusted.

so just calculate the length beforehand and use `memcpy` to copy exactly
as much as needed (as opposed to `strncpy` which always writes `n`
bytes).
2022-09-02 13:00:48 +02:00
NRK 32db2b1251 readstdin: use getline(3)
currently readstdin():
   - fgets() into a local buffer,
   - strchr() the buffer to eleminate the newline
   - stdups() the buffer into items

a simpler way is to just use getline(3), which will do the allocation
for us; eliminating the need for stdup()-ing.

additionally getline returns back the amount of bytes read, which
eliminates the need for strchr()-ing to find the newline.
2022-09-02 12:53:34 +02:00
Hiltjo Posthuma e35976f4a5 sync code-style patch from libsl 2022-08-08 10:42:54 +02:00
Hiltjo Posthuma 28fb3e2812 Makefile: add manual path for OpenBSD 2022-05-01 18:38:25 +02:00
Hiltjo Posthuma fe5d5c6709 fix incorrect comment, math is hard 2022-04-30 13:19:33 +02:00
Hiltjo Posthuma e1e1de7b3b inputw: improve correctness and startup performance, by NRK
Always use ~30% of the monitor width for the input in horizontal mode.

Patch adapted from NRK patches.
This also does not calculate inputw when using vertical mode anymore (because
the code is removed).
2022-04-29 20:18:02 +02:00
NRK 33685b06e9 drw_text: account for fallback fonts in ellipsis_width
additionally, ellipsis_width (which shouldn't change) is made static to
avoid re-calculating it on each drw_text() call.
2022-04-16 16:21:01 +02:00
NRK e4827b0c40 drw_text: don't segfault when called with 0 width
this patch just rejects *any* 0 width draws, which is surely an error by
the caller.

this also guards against cases where the width is too small for the
ellipsis to fit, so ellipsis_w will remain 0.
reported by Bakkeby <bakkeby@gmail.com>
2022-04-16 16:21:01 +02:00
9 changed files with 131 additions and 84 deletions

View File

@ -10,12 +10,12 @@ all: options dmenu stest
options:
@echo dmenu build options:
@echo "CFLAGS = $(CFLAGS)"
@echo "LDFLAGS = $(LDFLAGS)"
@echo "CFLAGS = $(XCFLAGS)"
@echo "LDFLAGS = $(XLDFLAGS)"
@echo "CC = $(CC)"
.c.o:
$(CC) -c $(CFLAGS) $<
$(CC) -c $(XCFLAGS) $<
config.h:
cp config.def.h $@
@ -23,10 +23,10 @@ config.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)
$(CC) -o $@ dmenu.o drw.o util.o $(XLDFLAGS)
stest: stest.o
$(CC) -o $@ stest.o $(LDFLAGS)
$(CC) -o $@ stest.o $(XLDFLAGS)
clean:
rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz

View File

@ -21,3 +21,6 @@ static unsigned int lines = 0;
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";
/* -n option; preselected item starting from 0 */
static unsigned int preselected = 0;

26
config.h Normal file
View File

@ -0,0 +1,26 @@
/* 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 */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
"monospace:size=10", "IPAGothic:size=10", "Symbola:size=10"
};
static const char *prompt = NULL; /* -p option; prompt to the left of input field */
static const char *colors[SchemeLast][2] = {
/* fg bg */
[SchemeNorm] = { "#bbbbbb", "#000000" },
[SchemeSel] = { "#eeeeee", "#005577" },
[SchemeOut] = { "#000000", "#00ffff" },
};
/* -l option; if nonzero, dmenu uses 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[] = " /?\"&[].:";
/* -n option; preselected item starting from 0 */
static unsigned int preselected = 0;

View File

@ -1,5 +1,5 @@
# dmenu version
VERSION = 5.1
VERSION = 5.2
# paths
PREFIX = /usr/local
@ -17,15 +17,16 @@ FREETYPELIBS = -lfontconfig -lXft
FREETYPEINC = /usr/include/freetype2
# OpenBSD (uncomment)
#FREETYPEINC = $(X11INC)/freetype2
#MANPREFIX = ${PREFIX}/man
# 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)
XCPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
XCFLAGS = -std=c99 -pedantic -Wall $(INCS) $(XCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
XLDFLAGS = $(LIBS) $(LDFLAGS)
# compiler and linker
CC = cc

View File

@ -22,6 +22,8 @@ dmenu \- dynamic menu
.IR color ]
.RB [ \-w
.IR windowid ]
.RB [ \-n
.IR number ]
.P
.BR dmenu_run " ..."
.SH DESCRIPTION
@ -80,6 +82,9 @@ prints version information to stdout, then exits.
.TP
.BI \-w " windowid"
embed into windowid.
.TP
.BI \-n " number"
preseslected item starting from 0.
.SH USAGE
dmenu is completely controlled by the keyboard. Items are selected using the
arrow keys, page up, page down, home, and end.

97
dmenu.c
View File

@ -22,7 +22,6 @@
/* 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 */
@ -324,19 +323,19 @@ movewordedge(int dir)
static void
keypress(XKeyEvent *ev)
{
char buf[32];
char buf[64];
int len;
KeySym ksym;
KeySym ksym = NoSymbol;
Status status;
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
switch (status) {
default: /* XLookupNone, XBufferOverflow */
return;
case XLookupChars:
case XLookupChars: /* composed string from input method */
goto insert;
case XLookupKeySym:
case XLookupBoth:
case XLookupBoth: /* a KeySym and a string are returned: use keysym */
break;
}
@ -358,13 +357,12 @@ keypress(XKeyEvent *ev)
case XK_n: ksym = XK_Down; break;
case XK_p: ksym = XK_Up; break;
case XK_u: /* delete left */
insert(NULL, 0 - cursor);
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);
@ -517,9 +515,9 @@ insert:
case XK_Tab:
if (!sel)
return;
strncpy(text, sel->text, sizeof text - 1);
text[sizeof text - 1] = '\0';
cursor = strlen(text);
cursor = strnlen(sel->text, sizeof text - 1);
memcpy(text, sel->text, cursor);
text[cursor] = '\0';
match();
break;
}
@ -549,20 +547,25 @@ paste(void)
static void
readstdin(void)
{
char buf[sizeof text], *p;
size_t i, size = 0;
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; fgets(buf, sizeof buf, stdin); i++) {
if (i + 1 >= size / sizeof *items)
if (!(items = realloc(items, (size += BUFSIZ))))
die("cannot realloc %zu bytes:", size);
if ((p = strchr(buf, '\n')))
*p = '\0';
if (!(items[i].text = strdup(buf)))
die("cannot strdup %zu bytes:", strlen(buf) + 1);
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);
@ -574,7 +577,7 @@ run(void)
XEvent ev;
while (!XNextEvent(dpy, &ev)) {
if (XFilterEvent(&ev, win))
if (XFilterEvent(&ev, None))
continue;
switch(ev.type) {
case DestroyNotify:
@ -610,13 +613,12 @@ static void
setup(void)
{
int x, y, i, j;
unsigned int du, tmp;
unsigned int du;
XSetWindowAttributes swa;
XIM xim;
Window w, dw, *dws;
XWindowAttributes wa;
XClassHint ch = {"dmenu", "dmenu"};
struct item *item;
#ifdef XINERAMA
XineramaScreenInfo *info;
Window pw;
@ -674,33 +676,49 @@ setup(void)
mw = wa.width;
}
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
for (item = items; item && item->text; ++item) {
if ((tmp = textw_clamp(item->text, mw/3)) > inputw) {
if ((inputw = tmp) == mw/3)
break;
inputw = mw / 3; /* input width: ~33% of monitor width */
match();
for (i = 0; i < preselected; i++) {
if (sel && sel->right && (sel = sel->right) == next) {
curr = next;
calcoffsets();
}
}
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,
win = XCreateWindow(dpy, root, 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");
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) {
XSetLocaleModifiers("@im=local");
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) {
XSetLocaleModifiers("@im=");
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);
XNClientWindow, embed ? win : parentwin, XNFocusWindow, embed ? win : parentwin, NULL);
XMapRaised(dpy, win);
XVaNestedList preedit_attr;
XPoint spot;
spot.x = x;
spot.y = topbar ? y + mh: 0;
preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
if (embed) {
XReparentWindow(dpy, win, parentwin, x, y);
XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
for (i = 0; i < du && dws[i] != win; ++i)
@ -716,9 +734,8 @@ setup(void)
static void
usage(void)
{
fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
" [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
exit(1);
die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
" [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
}
int
@ -760,11 +777,15 @@ main(int argc, char *argv[])
colors[SchemeSel][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
else if (!strcmp(argv[i], "-n")) /* preselected item */
preselected = atoi(argv[++i]);
else
usage();
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr);
if (!XSetLocaleModifiers(""))
fputs("warning: no locale modifiers support\n", stderr);
if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display");
screen = DefaultScreen(dpy);

41
drw.c
View File

@ -133,19 +133,6 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
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;
@ -251,8 +238,8 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int
int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
{
int i, ty, ellipsis_x = 0;
unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, ellipsis_width;
int ty, ellipsis_x = 0;
unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1;
XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont;
int utf8strlen, utf8charlen, render = x || y || w || h;
@ -264,10 +251,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
XftResult result;
int charexists = 0, overflow = 0;
/* keep track of a couple codepoints for which we have no match. */
enum { nomatches_len = 64 };
static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
static unsigned int nomatches[128], ellipsis_width;
if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
return 0;
if (!render) {
@ -283,7 +269,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
}
usedfont = drw->fonts;
drw_font_getexts(usedfont, "...", 3, &ellipsis_width, NULL);
if (!ellipsis_width && render)
ellipsis_width = drw_fontset_getwidth(drw, "...");
while (1) {
ew = ellipsis_len = utf8strlen = 0;
utf8str = text;
@ -349,11 +336,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
* character must be drawn. */
charexists = 1;
for (i = 0; i < nomatches_len; ++i) {
/* avoid calling XftFontMatch if we know we won't find a match */
if (utf8codepoint == nomatches.codepoint[i])
goto no_match;
}
hash = (unsigned int)utf8codepoint;
hash = ((hash >> 16) ^ hash) * 0x21F0AAAD;
hash = ((hash >> 15) ^ hash) * 0xD35A2D97;
h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches);
h1 = (hash >> 17) % LENGTH(nomatches);
/* avoid expensive XftFontMatch call when we know we won't find a match */
if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint)
goto no_match;
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint);
@ -366,7 +356,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
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);
@ -383,7 +372,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
curfont->next = usedfont;
} else {
xfont_free(usedfont);
nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint;
no_match:
usedfont = drw->fonts;
}

23
util.c
View File

@ -6,18 +6,9 @@
#include "util.h"
void *
ecalloc(size_t nmemb, size_t size)
{
void *p;
if (!(p = calloc(nmemb, size)))
die("calloc:");
return p;
}
void
die(const char *fmt, ...) {
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
@ -33,3 +24,13 @@ die(const char *fmt, ...) {
exit(1);
}
void *
ecalloc(size_t nmemb, size_t size)
{
void *p;
if (!(p = calloc(nmemb, size)))
die("calloc:");
return p;
}

1
util.h
View File

@ -3,6 +3,7 @@
#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])
void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);