From 5687f4696472ba6029bbba18e293e3e8b9e154ea Mon Sep 17 00:00:00 2001 From: Pontus Stenetorp Date: Sat, 8 Jun 2024 19:36:15 +0900 Subject: [PATCH 01/15] Add missing void to updateclientlist definition Caught by -pedantic implying -Wstrict-prototypes for OpenBSD's 16.0.6 Clang. --- dwm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwm.c b/dwm.c index f1d86b2..67c6b2b 100644 --- a/dwm.c +++ b/dwm.c @@ -1851,7 +1851,7 @@ updatebarpos(Monitor *m) } void -updateclientlist() +updateclientlist(void) { Client *c; Monitor *m; From 8933ebcf50024f4378a78e556b1ac08091197206 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 5 Oct 2024 13:01:49 +0200 Subject: [PATCH 02/15] sync drw.{c,h} from dmenu - drw: minor improvement to the nomatches cache - overhaul utf8decoding and render invalid utf8 sequences as U+FFFD. Thanks NRK for these improvements! --- drw.c | 114 ++++++++++++++++++++++++++++----------------------------- dwm.c | 1 - util.h | 1 + 3 files changed, 56 insertions(+), 60 deletions(-) diff --git a/drw.c b/drw.c index a58a2b4..344de61 100644 --- a/drw.c +++ b/drw.c @@ -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; } @@ -238,11 +224,11 @@ 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; + 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; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; @@ -251,9 +237,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. */ - enum { nomatches_len = 64 }; - static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; - static unsigned int ellipsis_width = 0; + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; @@ -273,12 +258,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) { @@ -300,9 +287,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; } @@ -310,7 +297,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; @@ -325,6 +312,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); @@ -338,11 +331,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); @@ -371,7 +367,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; } diff --git a/dwm.c b/dwm.c index 67c6b2b..1443802 100644 --- a/dwm.c +++ b/dwm.c @@ -50,7 +50,6 @@ #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) #define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) -#define LENGTH(X) (sizeof X / sizeof X[0]) #define MOUSEMASK (BUTTONMASK|PointerMotionMask) #define WIDTH(X) ((X)->w + 2 * (X)->bw) #define HEIGHT(X) ((X)->h + 2 * (X)->bw) diff --git a/util.h b/util.h index f633b51..c0a50d4 100644 --- a/util.h +++ b/util.h @@ -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); From fcb2476b693ca4c40ad32c7119e27bbeb856865c Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 27 Oct 2024 20:10:07 +0100 Subject: [PATCH 03/15] util.c: output function might override errno and thus affect perror() Original patch by Raymond Cole with some modifications, thanks! --- util.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/util.c b/util.c index 96b82c9..8e26a51 100644 --- a/util.c +++ b/util.c @@ -1,4 +1,5 @@ /* See LICENSE file for copyright and license details. */ +#include #include #include #include @@ -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); } From cfb8627a80a334f200f68c2c8f3e384313ebbaf5 Mon Sep 17 00:00:00 2001 From: Raymond Cole Date: Mon, 28 Oct 2024 00:34:55 +0000 Subject: [PATCH 04/15] Avoid unsigned integer underflow in drw_text() --- drw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drw.c b/drw.c index 344de61..c41e6af 100644 --- a/drw.c +++ b/drw.c @@ -248,6 +248,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)); From 693d94d350c806e77677c35958e18590c26e19d2 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 9 Aug 2025 14:34:03 +0200 Subject: [PATCH 05/15] bump version to 6.6 --- config.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mk b/config.mk index 8efca9a..b469a2b 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dwm version -VERSION = 6.5 +VERSION = 6.6 # Customize below to fit your system From 74edc27caa65aba9ea8d1fe03d26e3b449f79590 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 12 Aug 2025 19:17:20 +0200 Subject: [PATCH 06/15] config: make refreshrate for mouse move/resize a config option Bump the default from 60 to 120. --- config.def.h | 1 + dwm.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.def.h b/config.def.h index 9efa774..81c3fc0 100644 --- a/config.def.h +++ b/config.def.h @@ -36,6 +36,7 @@ static const float mfact = 0.55; /* factor of master area size [0.05..0.95] static const int nmaster = 1; /* number of clients in master area */ static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ +static const int refreshrate = 120; /* refresh rate (per second) for client move/resize */ static const Layout layouts[] = { /* symbol arrange function */ diff --git a/dwm.c b/dwm.c index 1443802..4cf07eb 100644 --- a/dwm.c +++ b/dwm.c @@ -1171,7 +1171,7 @@ movemouse(const Arg *arg) handler[ev.type](&ev); break; case MotionNotify: - if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + if ((ev.xmotion.time - lasttime) <= (1000 / refreshrate)) continue; lasttime = ev.xmotion.time; @@ -1325,7 +1325,7 @@ resizemouse(const Arg *arg) handler[ev.type](&ev); break; case MotionNotify: - if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + if ((ev.xmotion.time - lasttime) <= (1000 / refreshrate)) continue; lasttime = ev.xmotion.time; From 93f26863d14666e56e992de3670a77178e66ddf2 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 27 Sep 2025 12:10:17 +0200 Subject: [PATCH 07/15] cleanup schemes and colors --- drw.c | 28 +++++++++++++++++++++++++--- drw.h | 2 ++ dwm.c | 4 +++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/drw.c b/drw.c index c41e6af..98dbaa8 100644 --- a/drw.c +++ b/drw.c @@ -178,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) { @@ -187,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++) @@ -195,6 +194,29 @@ 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]); +} + void drw_setfontset(Drw *drw, Fnt *set) { diff --git a/drw.h b/drw.h index 6471431..bda06f9 100644 --- a/drw.h +++ b/drw.h @@ -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); diff --git a/dwm.c b/dwm.c index 4cf07eb..21cf8fd 100644 --- a/dwm.c +++ b/dwm.c @@ -485,8 +485,10 @@ cleanup(void) cleanupmon(mons); for (i = 0; i < CurLast; i++) drw_cur_free(drw, cursor[i]); - for (i = 0; i < LENGTH(colors); i++) + for (i = 0; i < LENGTH(colors); i++) { + drw_scm_free(drw, scheme[i], 3); free(scheme[i]); + } free(scheme); XDestroyWindow(dpy, wmcheckwin); drw_free(drw); From 7c3abae4e68b6a21f05cb04f3af31217259c0aa9 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 29 Sep 2025 18:48:27 +0200 Subject: [PATCH 08/15] drw.c: drw_scm_free: call free inside Because drw_scm_create() allocates it. --- drw.c | 1 + dwm.c | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drw.c b/drw.c index 98dbaa8..9fdd1a4 100644 --- a/drw.c +++ b/drw.c @@ -215,6 +215,7 @@ drw_scm_free(Drw *drw, Clr *scm, size_t clrcount) for (i = 0; i < clrcount; i++) drw_clr_free(drw, &scm[i]); + free(scm); } void diff --git a/dwm.c b/dwm.c index 21cf8fd..4f345ee 100644 --- a/dwm.c +++ b/dwm.c @@ -485,10 +485,8 @@ cleanup(void) cleanupmon(mons); for (i = 0; i < CurLast; i++) drw_cur_free(drw, cursor[i]); - for (i = 0; i < LENGTH(colors); i++) { + for (i = 0; i < LENGTH(colors); i++) drw_scm_free(drw, scheme[i], 3); - free(scheme[i]); - } free(scheme); XDestroyWindow(dpy, wmcheckwin); drw_free(drw); From 244fa852fe2775cf52a3901966cd6d8700df8227 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 7 Jan 2026 22:02:00 +0800 Subject: [PATCH 09/15] dwm: Fix heap buffer overflow in getatomprop When getatomprop() is called, it invokes XGetWindowProperty() to retrieve an Atom. If the property exists but has zero elements (length 0), Xlib returns Success and sets p to a valid, non-NULL memory address containing a single null byte. However, dl (that is, the number of items) is 0. dwm blindly casts p to Atom* and dereferences it. While Xlib guarantees that p is safe to read as a string (that is, it is null-terminated), it does _not_ guarantee it is safe to read as an Atom (an unsigned long). The Atom type is a typedef for unsigned long. Reading an Atom (which thus will either likely be 4 or 8 bytes) from a 1-byte allocated buffer results in a heap buffer overflow. Since property content is user controlled, this allows any client to trigger an out of bounds read simply by setting a property with format 32 and length 0. An example client which reliably crashes dwm under ASAN: #include #include #include #include #include int main(void) { Display *d; Window root, w; Atom net_wm_state; d = XOpenDisplay(NULL); if (!d) return 1; root = DefaultRootWindow(d); w = XCreateSimpleWindow(d, root, 10, 10, 200, 200, 1, 0, 0); net_wm_state = XInternAtom(d, "_NET_WM_STATE", False); if (net_wm_state == None) return 1; XChangeProperty(d, w, net_wm_state, XA_ATOM, 32, PropModeReplace, NULL, 0); XMapWindow(d, w); XSync(d, False); sleep(1); XCloseDisplay(d); return 0; } In order to avoid this, check that the number of items returned is greater than zero before dereferencing the pointer. --- dwm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dwm.c b/dwm.c index 4f345ee..8f4fa75 100644 --- a/dwm.c +++ b/dwm.c @@ -870,7 +870,8 @@ getatomprop(Client *c, Atom prop) if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, &da, &di, &dl, &dl, &p) == Success && p) { - atom = *(Atom *)p; + if (dl > 0) + atom = *(Atom *)p; XFree(p); } return atom; From 85fe518c1af5eb43f222f4d8579e4814ed769f3b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 10 Jan 2026 11:31:44 +0100 Subject: [PATCH 10/15] bump version to 6.7 Put the maintainer at the top and bump years (time flies). --- LICENSE | 2 +- config.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 995172f..596e6cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ MIT/X Consortium License +© 2010-2026 Hiltjo Posthuma © 2006-2019 Anselm R Garbe © 2006-2009 Jukka Salmi © 2006-2007 Sander van Dijk @@ -11,7 +12,6 @@ MIT/X Consortium License © 2008 Martin Hurton © 2008 Neale Pickett © 2009 Mate Nagy -© 2010-2016 Hiltjo Posthuma © 2010-2012 Connor Lane Smith © 2011 Christoph Lohmann <20h@r-36.net> © 2015-2016 Quentin Rameau diff --git a/config.mk b/config.mk index b469a2b..6e875f1 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dwm version -VERSION = 6.6 +VERSION = 6.7 # Customize below to fit your system From a9aa0d8ffbb548b0b1f9f755557aef2482c0f820 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 14 Jan 2026 14:58:05 +0800 Subject: [PATCH 11/15] dwm: Fix getatomprop regression from heap overflow fix Commit 244fa852fe27 ("dwm: Fix heap buffer overflow in getatomprop") introduced a check for dl > 0 before dereferencing the property pointer. However, I missed that the variable dl is passed to XGetWindowProperty for both nitems_return and bytes_after_return parameters: XGetWindowProperty(..., &dl, &dl, &p) The final value in dl is bytes_after_return, not nitems_return. For a successfully read property, bytes_after is typically 0 (indicating all data was retrieved), so the check `dl > 0` is always false and dwm never reads any atom properties. So this is safe, but not very helpful :-) dl is probably just a dummy variable anyway, so fix by using a separate variable for nitems, and check nitems > 0 as originally intended. --- dwm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dwm.c b/dwm.c index 8f4fa75..53b393e 100644 --- a/dwm.c +++ b/dwm.c @@ -864,13 +864,13 @@ Atom getatomprop(Client *c, Atom prop) { int di; - unsigned long dl; + unsigned long nitems, dl; unsigned char *p = NULL; Atom da, atom = None; if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, - &da, &di, &dl, &dl, &p) == Success && p) { - if (dl > 0) + &da, &di, &nitems, &dl, &p) == Success && p) { + if (nitems > 0) atom = *(Atom *)p; XFree(p); } From f63cde9354504ee9cfecc07517c03736d0f90c26 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 30 Jan 2026 11:18:38 +0100 Subject: [PATCH 12/15] bump version to 6.8 --- config.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mk b/config.mk index 6e875f1..982dc21 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dwm version -VERSION = 6.7 +VERSION = 6.8 # Customize below to fit your system From 397d618f1cfbed398ef05d0c9d1e5dbcdb8144e7 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 12 Feb 2026 22:28:02 +0000 Subject: [PATCH 13/15] fix not updating _NET_ACTIVE_WINDOW currently clients that set the input field of WM_HINTS to true (c->neverfocus) will never be updated as _NET_ACTIVE_WINDOW even when they are focused. according to the ICCCM [0] the input field of WM_HINTS tells the WM to either use or not use XSetInputFocus(), it shouldn't have any relation to _NET_ACTIVE_WINDOW. EWMH spec [1] also does not mention any relationship between the two. this issue was noticed when launching games via steam/proton and noticing that _NET_ACTIVE_WINDOW was always wrong/stale (i.e not updated to the game window). for reference I've looked at bspwm [2] and it also seems to set _NET_ACTIVE_WINDOW regardless of whether the client has WM_HINTS input true or not. [0]: https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#input_focus [1]: https://specifications.freedesktop.org/wm/1.5/ar01s03.html#id-1.4.10 [2]: https://github.com/baskerville/bspwm/blob/c5cf7d3943f9a34a5cb2bab36bf473fd77e7d4f6/src/tree.c#L659-L662 --- dwm.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dwm.c b/dwm.c index 53b393e..fc4232e 100644 --- a/dwm.c +++ b/dwm.c @@ -1470,12 +1470,10 @@ sendevent(Client *c, Atom proto) void setfocus(Client *c) { - if (!c->neverfocus) { + if (!c->neverfocus) XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); - XChangeProperty(dpy, root, netatom[NetActiveWindow], - XA_WINDOW, 32, PropModeReplace, - (unsigned char *) &(c->win), 1); - } + XChangeProperty(dpy, root, netatom[NetActiveWindow], XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&c->win, 1); sendevent(c, wmatom[WMTakeFocus]); } From 5c9f30300bec2f7eec9ba61d0c11df999e17f860 Mon Sep 17 00:00:00 2001 From: NRK Date: Sun, 15 Feb 2026 22:59:13 +0000 Subject: [PATCH 14/15] getstate: fix access type and remove redundant cast WM_STATE is defined to be format == 32 which xlib returns as `long` and so accessing it as `unsigned char` is incorrect. and also &p is already an `unsigned char **` and so the cast was completely redundant. given the redundant cast, i assume `p` was `long *` at some time but was changed to `unsigned char *` later, but the pointer access (and the cast) wasn't updated. also add a `format == 32` check as safety measure before accessing, just in case. --- dwm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dwm.c b/dwm.c index fc4232e..a5e1ce9 100644 --- a/dwm.c +++ b/dwm.c @@ -897,10 +897,10 @@ getstate(Window w) Atom real; if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], - &real, &format, &n, &extra, (unsigned char **)&p) != Success) + &real, &format, &n, &extra, &p) != Success) return -1; - if (n != 0) - result = *p; + if (n != 0 && format == 32) + result = *(long *)p; XFree(p); return result; } From c3dd6a829b3f5cb9474bcca787a9c8a86932d75d Mon Sep 17 00:00:00 2001 From: NRK Date: Tue, 17 Feb 2026 07:31:35 +0000 Subject: [PATCH 15/15] more overflow fix in getatomprop() commit 244fa852 (and a9aa0d8) tried to fix overflow by checking the number of items returned. however this is not sufficient since the format may be lower than 32 bits. to reproduce the crash, i used the reproducer given in commit 244fa85 but changed the XChangeProperty line to the following to set the property to a 1 element 16 bit item: short si = 1; XChangeProperty(d, w, net_wm_state, XA_ATOM, 16, PropModeReplace, (unsigned char *)&si, 1); this client reliably crashes dwm under ASAN since dwm is trying to read a 32 bit value from a 16 bit one. fix it by checking for format == 32 as well. also change the access type from Atom to long, on my machine Atom is typedef-ed to long already but that may not be true everywere. the XGetWindowProperty manpage says format == 32 is returned as `long` so use `long` directly. (N.B: it also might be worth checking if the returned type is XA_ATOM as well, but i wasn't able to cause any crashes by setting different types so i'm leaving it out for now.) --- dwm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dwm.c b/dwm.c index a5e1ce9..0a67103 100644 --- a/dwm.c +++ b/dwm.c @@ -863,15 +863,15 @@ focusstack(const Arg *arg) Atom getatomprop(Client *c, Atom prop) { - int di; + int format; unsigned long nitems, dl; unsigned char *p = NULL; Atom da, atom = None; if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, - &da, &di, &nitems, &dl, &p) == Success && p) { - if (nitems > 0) - atom = *(Atom *)p; + &da, &format, &nitems, &dl, &p) == Success && p) { + if (nitems > 0 && format == 32) + atom = *(long *)p; XFree(p); } return atom;