Compare commits

...
17 Commits
Author SHA1 Message Date
Hiltjo Posthuma bfb071698a getline: add check for len > 0
This condition cannot happen here though.
2026-10-27 21:10:35 +01:00
Hiltjo Posthuma 7c0334de3d check integer overflow for multiplication before realloc 2026-10-27 21:09:38 +01:00
Hiltjo Posthuma 8627d0c659 check return value of XCreateIC() 2026-10-16 13:05:13 +02:00
Hiltjo Posthuma 61e0072c3e keypress, check if len > 0 just in case before reading buf
I'm not sure if this can happen, but check it just in case:

Maybe possible when:

len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
len == 0 and case XLookupChars would be executed on a keypress.

Reported by: jb19357 <jb19357@protonmail.com>
Who used Claude to write a report.

I cannot reproduce this issue, but it seems logical.
2026-09-13 20:48:49 +02:00
Hiltjo Posthuma 0785a28f1d fix out-of-bounds read of the Xinerama screen array with no screens
This can happen if dmenu is run with Xinerama enabled and no screens are
detected.

Also fallback to the first screen if there is no intersection of the root
pointer to the screens found.

Reported by: jb19357 <jb19357@protonmail.com>
Who used Claude to write a report.

I cannot reproduce this issue, but it seems logical.
2026-09-13 20:39:14 +02:00
Hiltjo Posthuma 5453bb65d9 fix crash with long prompt and pressing the end key in horizontal mode
Reproducable with:

printf 'a\nb\n' | ./dmenu -p "$(printf 'x%.0s' $(seq 1 400))"

and then pressing the end key.

Reported by: jb19357 <jb19357@protonmail.com>
Who used Claude to write a report.
2026-09-13 20:31:37 +02:00
Julien Addé 7175c4880b add -of and -ob arguments for outline colors options for multi-selection 2026-01-30 11:09:51 +01:00
Hiltjo Posthuma 8b48986f4c drw.c: drw_scm_free: call free inside
Because drw_scm_create() allocates it.
2025-09-29 18:47:16 +02:00
Hiltjo Posthuma 77f96d704b cleanup schemes and colors 2025-09-27 12:10:37 +02:00
Hiltjo Posthuma d893c63cdd bump version to 5.4 2025-08-09 14:33:40 +02:00
Adam Purkrt b1e217b29a XUngrabKeyboard() instead of XUngrabKey()
XUngrabKey(), which is currently used in cleanup(), is not the right
counterpart to XGrabKeyboard(), which is used in grabkeyboard(),
called from main().

XUngrabKeyboard() is the function to use, as grabbing the whole
keyboard is different to grabbing individual keys.

With the current code the keyboard gets ungrabbed, as far as I can tell,
only by the final XCloseDisplay() in cleanup(), as the XUngrabKey()
there effectively does nothing.
2025-03-17 21:01:05 +01:00
Hiltjo Posthuma 86f0b5119e remove extra newline 2024-10-30 13:03:25 +01:00
Hiltjo Posthuma dd3d348ae8 Avoid unsigned integer underflow in drw_text()
Patch by Raymond Cole <rc@wolog.xyz>, thanks.
2024-10-30 13:02:57 +01:00
Hiltjo Posthuma 545031a076 util.c: output function might override errno and thus affect perror()
Original patch by Raymond Cole with some modifications, thanks!
2024-10-27 20:08:46 +01:00
Hiltjo Posthuma 475d8093cb drw.c: use the same pattern as ellipsis_width to check for infinite recursion 2024-07-14 11:43:01 +02:00
NRK 59936c7d97 render invalid utf8 sequences as U+FFFD
previously drw_text would do the width calculations as if
invalid utf8 sequences were replaced with U+FFFD but would pass
the invalid utf8 sequence to xft to render where xft would just
cut it off at the first invalid byte.

this change makes invalid utf8 render as U+FFFD and avoids
sending invalid sequences to xft. the following can be used to
check the behavior before and after the patch:

	$ printf "0\xef1234567\ntest" | dmenu

Ref: https://lists.suckless.org/dev/2407/35646.html
2024-07-14 11:42:58 +02:00
NRK 51e32d49b5 overhaul utf8decode()
this changes the utf8decode function to:

* report when an error occurs
* report how many bytes to advance on error

these will be useful in the next commit to render invalid utf8
sequences.

the new implementation is also shorter and more direct.
2024-07-14 11:42:55 +02:00
6 changed files with 112 additions and 67 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
# dmenu version
VERSION = 5.3
VERSION = 5.4
# paths
PREFIX = /usr/local
+10
View File
@@ -20,6 +20,10 @@ dmenu \- dynamic menu
.IR color ]
.RB [ \-sf
.IR color ]
.RB [ \-ob
.IR color ]
.RB [ \-of
.IR color ]
.RB [ \-w
.IR windowid ]
.P
@@ -75,6 +79,12 @@ defines the selected background color.
.BI \-sf " color"
defines the selected foreground color.
.TP
.BI \-ob " color"
defines the outline background color (for multiple selection).
.TP
.BI \-of " color"
defines the outline foreground color (for multiple selection).
.TP
.B \-v
prints version information to stdout, then exits.
.TP
+20 -8
View File
@@ -1,6 +1,7 @@
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -100,9 +101,9 @@ cleanup(void)
{
size_t i;
XUngrabKey(dpy, AnyKey, AnyModifier, root);
XUngrabKeyboard(dpy, CurrentTime);
for (i = 0; i < SchemeLast; i++)
free(scheme[i]);
drw_scm_free(drw, scheme[i], 2);
for (i = 0; items && items[i].text; ++i)
free(items[i].text);
free(items);
@@ -171,7 +172,7 @@ drawmenu(void)
/* draw vertical list */
for (item = curr; item != next; item = item->right)
drawitem(item, x, y += bh, mw - x);
} else if (matches) {
} else if (matches && curr) {
/* draw horizontal list */
x += inputw;
w = TEXTW("<");
@@ -414,7 +415,7 @@ keypress(XKeyEvent *ev)
switch(ksym) {
default:
insert:
if (!iscntrl((unsigned char)*buf))
if (len > 0 && !iscntrl((unsigned char)*buf))
insert(buf, len);
break;
case XK_Delete:
@@ -556,10 +557,12 @@ readstdin(void)
for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
if (i + 1 >= itemsiz) {
itemsiz += 256;
if (itemsiz > SIZE_MAX / sizeof(*items))
die("too many items");
if (!(items = realloc(items, itemsiz * sizeof(*items))))
die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
}
if (line[len - 1] == '\n')
if (len > 0 && line[len - 1] == '\n')
line[len - 1] = '\0';
if (!(items[i].text = strdup(line)))
die("strdup:");
@@ -639,6 +642,8 @@ setup(void)
#ifdef XINERAMA
i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
if (n < 1)
die("Xinerama reported no screens");
XGetInputFocus(dpy, &w, &di);
if (mon >= 0 && mon < n)
i = mon;
@@ -661,6 +666,9 @@ setup(void)
for (i = 0; i < n; i++)
if (INTERSECT(x, y, 1, 1, info[i]) != 0)
break;
/* fallback to the first screen if there is no intersection */
if (i >= n)
i = 0;
x = info[i].x_org;
y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
@@ -689,13 +697,13 @@ setup(void)
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);
if ((xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL)) == NULL)
die("XCreateIC failed: could not obtain input context");
XMapRaised(dpy, win);
if (embed) {
@@ -756,6 +764,10 @@ main(int argc, char *argv[])
colors[SchemeSel][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
colors[SchemeSel][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-ob")) /* outline background color */
colors[SchemeOut][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-of")) /* outline foreground color */
colors[SchemeOut][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
else
+72 -52
View File
@@ -9,54 +9,40 @@
#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)
static int
utf8decode(const char *s_in, long *u, int *err)
{
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;
static const unsigned char lens[] = {
/* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */
/* 110XX */ 2, 2, 2, 2,
/* 1110X */ 3, 3,
/* 11110 */ 4,
/* 11111 */ 0, /* invalid */
};
static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 };
static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 };
const unsigned char *s = (const unsigned char *)s_in;
int len = lens[*s >> 3];
*u = UTF_INVALID;
if (!clen)
return 0;
udecoded = utf8decodebyte(c[0], &len);
if (!BETWEEN(len, 1, UTF_SIZ))
*err = 1;
if (len == 0)
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);
long cp = s[0] & leading_mask[len - 1];
for (int i = 1; i < len; ++i) {
if (s[i] == '\0' || (s[i] & 0xC0) != 0x80)
return i;
cp = (cp << 6) | (s[i] & 0x3F);
}
/* out of range, surrogate, overlong encoding */
if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1])
return len;
*err = 0;
*u = cp;
return len;
}
@@ -192,8 +178,7 @@ drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
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. */
/* Create color schemes. */
Clr *
drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
{
@@ -201,7 +186,7 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
Clr *ret;
/* need at least two colors for a scheme */
if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(Clr))))
return NULL;
for (i = 0; i < clrcount; i++)
@@ -209,6 +194,30 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
return ret;
}
void
drw_clr_free(Drw *drw, Clr *c)
{
if (!drw || !c)
return;
/* c is typedef XftColor Clr */
XftColorFree(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen), c);
}
void
drw_scm_free(Drw *drw, Clr *scm, size_t clrcount)
{
size_t i;
if (!drw || !scm)
return;
for (i = 0; i < clrcount; i++)
drw_clr_free(drw, &scm[i]);
free(scm);
}
void
drw_setfontset(Drw *drw, Fnt *set)
{
@@ -242,7 +251,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
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;
int utf8strlen, utf8charlen, utf8err, render = x || y || w || h;
long utf8codepoint = 0;
const char *utf8str;
FcCharSet *fccharset;
@@ -251,7 +260,8 @@ 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. */
static unsigned int nomatches[128], ellipsis_width;
static unsigned int nomatches[128], ellipsis_width, invalid_width;
static const char invalid[] = "�";
if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
return 0;
@@ -261,6 +271,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
} else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
if (w < lpad)
return x + w;
d = XftDrawCreate(drw->dpy, drw->drawable,
DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen));
@@ -271,12 +283,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
usedfont = drw->fonts;
if (!ellipsis_width && render)
ellipsis_width = drw_fontset_getwidth(drw, "...");
if (!invalid_width && render)
invalid_width = drw_fontset_getwidth(drw, invalid);
while (1) {
ew = ellipsis_len = utf8strlen = 0;
ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0;
utf8str = text;
nextfont = NULL;
while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
utf8charlen = utf8decode(text, &utf8codepoint, &utf8err);
for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) {
@@ -298,9 +312,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
else
utf8strlen = ellipsis_len;
} else if (curfont == usedfont) {
utf8strlen += utf8charlen;
text += utf8charlen;
ew += tmpw;
utf8strlen += utf8err ? 0 : utf8charlen;
ew += utf8err ? 0 : tmpw;
} else {
nextfont = curfont;
}
@@ -308,7 +322,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
}
}
if (overflow || !charexists || nextfont)
if (overflow || !charexists || nextfont || utf8err)
break;
else
charexists = 0;
@@ -323,6 +337,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
x += ew;
w -= ew;
}
if (utf8err && (!render || invalid_width < w)) {
if (render)
drw_text(drw, x, y, w, h, 0, invalid, invert);
x += invalid_width;
w -= invalid_width;
}
if (render && overflow)
drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
+2
View File
@@ -40,7 +40,9 @@ void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned in
/* Colorscheme abstraction */
void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
void drw_clr_free(Drw *drw, Clr *c);
Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
void drw_scm_free(Drw *drw, Clr *scm, size_t clrcount);
/* Cursor abstraction */
Cur *drw_cur_create(Drw *drw, int shape);
+7 -6
View File
@@ -1,4 +1,5 @@
/* See LICENSE file for copyright and license details. */
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -10,17 +11,17 @@ void
die(const char *fmt, ...)
{
va_list ap;
int saved_errno;
saved_errno = errno;
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);
}
if (fmt[0] && fmt[strlen(fmt)-1] == ':')
fprintf(stderr, " %s", strerror(saved_errno));
fputc('\n', stderr);
exit(1);
}