Re: cwm(1) without warping the pointer

Walter Alejandro Iglesias <[email protected]> Fri, 3 Jul 2026 14:17:45 +0200
Newsgroups gmane.os.openbsd.misc
Message-ID <[email protected]>
Last update brakes the "focus previous" I'd added to client_remove().
Fixed below.

I reached the tricky part, groups, which in the current implementation
already have some bug.  And to be totally honest, even though I've been
using cwm for quite a while, I still don't know what's the appeal of
groups compared to traditional desktop management, so I can't really
assess what behavior might be convenient in this case.

Anyways, considering this thread has been a monologue, I'm going to let
this proposal rest.  I only got feedback from one person, who said he
agreed with the change.  If more people show up (especially the
maintainer), I'll see if I continue with this.


By!


Index: calmwm.h
===================================================================
RCS file: /cvs/xenocara/app/cwm/calmwm.h,v
diff -u -p -u -p -r1.380 calmwm.h
--- calmwm.h	20 Aug 2025 23:44:06 -0000	1.380
+++ calmwm.h	3 Jul 2026 11:59:39 -0000
@@ -405,8 +405,9 @@ void			 client_config(struct client_ctx 
 struct client_ctx	*client_current(struct screen_ctx *);
 void			 client_draw_border(struct client_ctx *);
 struct client_ctx	*client_find(Window);
+void			 client_flush_enter_events(void);
 void			 client_get_sizehints(struct client_ctx *);
-void			 client_hide(struct client_ctx *);
+void			 client_hide(struct client_ctx *, int);
 void 			 client_htile(struct client_ctx *);
 int			 client_inbound(struct client_ctx *, int, int);
 struct client_ctx	*client_init(Window, struct screen_ctx *);
@@ -423,7 +424,7 @@ void			 client_remove(struct client_ctx 
 void			 client_resize(struct client_ctx *, int);
 void			 client_set_active(struct client_ctx *);
 void			 client_set_name(struct client_ctx *);
-void			 client_show(struct client_ctx *);
+void			 client_show(struct client_ctx *, int);
 int			 client_snapcalc(int, int, int, int, int);
 void			 client_toggle_hidden(struct client_ctx *);
 void			 client_toggle_hmaximize(struct client_ctx *);
Index: client.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/client.c,v
diff -u -p -u -p -r1.267 client.c
--- client.c	22 Mar 2023 08:27:36 -0000	1.267
+++ client.c	3 Jul 2026 11:59:39 -0000
@@ -123,9 +123,9 @@ client_init(Window win, struct screen_ct
 
 	xu_get_wm_state(cc->win, &state);
 	if (state == IconicState)
-		client_hide(cc);
+		client_hide(cc, 1);
 	else
-		client_show(cc);
+		client_show(cc, 1);
 
 	if (mapped) {
 		if (cc->gc) {
@@ -210,6 +210,7 @@ client_remove(struct client_ctx *cc)
 {
 	struct screen_ctx	*sc = cc->sc;
 	struct winname		*wn;
+	struct client_ctx	*prevcc;
 
 	TAILQ_REMOVE(&sc->clientq, cc, entry);
 
@@ -230,6 +231,18 @@ client_remove(struct client_ctx *cc)
 	free(cc->res_class);
 	free(cc->res_name);
 	free(cc);
+
+	if (TAILQ_EMPTY(&sc->clientq))
+	    return;
+
+	prevcc = TAILQ_FIRST(&sc->clientq);
+
+	if (prevcc->flags & (CLIENT_SKIP_CYCLE))
+		return;
+
+	client_raise(prevcc);
+	client_set_active(prevcc);
+	client_flush_enter_events();
 }
 
 void
@@ -336,7 +349,7 @@ client_toggle_fullscreen(struct client_c
 resize:
 	client_resize(cc, 0);
 	xu_ewmh_set_net_wm_state(cc);
-	client_ptr_inbound(cc, 1);
+	client_flush_enter_events();
 }
 
 void
@@ -377,7 +390,7 @@ client_toggle_maximize(struct client_ctx
 resize:
 	client_resize(cc, 0);
 	xu_ewmh_set_net_wm_state(cc);
-	client_ptr_inbound(cc, 1);
+	client_flush_enter_events();
 }
 
 void
@@ -410,7 +423,7 @@ client_toggle_vmaximize(struct client_ct
 resize:
 	client_resize(cc, 0);
 	xu_ewmh_set_net_wm_state(cc);
-	client_ptr_inbound(cc, 1);
+	client_flush_enter_events();
 }
 
 void
@@ -443,7 +456,7 @@ client_toggle_hmaximize(struct client_ct
 resize:
 	client_resize(cc, 0);
 	xu_ewmh_set_net_wm_state(cc);
-	client_ptr_inbound(cc, 1);
+	client_flush_enter_events();
 }
 
 void
@@ -516,8 +529,6 @@ client_ptr_inbound(struct client_ctx *cc
 		cc->ptr.y = 0;
 	else if (cc->ptr.y > cc->geom.h - 1)
 		cc->ptr.y = cc->geom.h - 1;
-
-	client_ptr_warp(cc);
 }
 
 void
@@ -542,8 +553,11 @@ client_ptr_save(struct client_ctx *cc)
 }
 
 void
-client_hide(struct client_ctx *cc)
+client_hide(struct client_ctx *cc, int focus)
 {
+	struct screen_ctx	*sc = cc->sc;
+	struct client_ctx	*prevcc;
+
 	XUnmapWindow(X_Dpy, cc->win);
 
 	if (cc->flags & CLIENT_ACTIVE) {
@@ -552,16 +566,48 @@ client_hide(struct client_ctx *cc)
 	}
 	cc->flags |= CLIENT_HIDDEN;
 	xu_set_wm_state(cc->win, IconicState);
+
+	if (!focus)
+		return;
+
+	if (TAILQ_EMPTY(&sc->clientq))
+		return;
+
+	prevcc = TAILQ_NEXT(cc, entry);
+	if (prevcc == NULL)
+		prevcc = TAILQ_FIRST(&sc->clientq);
+
+	while (prevcc != NULL && prevcc != cc) {
+		if (!(prevcc->flags & CLIENT_SKIP_CYCLE) &&
+		    !(prevcc->flags & CLIENT_HIDDEN))
+			break;
+		prevcc = TAILQ_NEXT(prevcc, entry);
+		if (prevcc == NULL)
+			prevcc = TAILQ_FIRST(&sc->clientq);
+	}
+
+	if (prevcc == NULL || prevcc == cc)
+		return;
+
+	client_raise(prevcc);
+	client_set_active(prevcc);
+	client_flush_enter_events();
 }
 
 void
-client_show(struct client_ctx *cc)
+client_show(struct client_ctx *cc, int focus)
 {
 	XMapRaised(X_Dpy, cc->win);
 
 	cc->flags &= ~CLIENT_HIDDEN;
 	xu_set_wm_state(cc->win, NormalState);
 	client_draw_border(cc);
+
+	if (!focus)
+		return;
+
+	client_set_active(cc);
+	client_flush_enter_events();
 }
 
 void
@@ -949,7 +995,6 @@ client_htile(struct client_ctx *cc)
 	if (Conf.htile > 0)
 		cc->geom.h = ((area.h - (cc->bwidth * 2)) * Conf.htile) / 100;
 	client_resize(cc, 1);
-	client_ptr_warp(cc);
 
 	mh = cc->geom.h + (cc->bwidth * 2);
 	x = area.x;
@@ -1018,7 +1063,6 @@ client_vtile(struct client_ctx *cc)
 		cc->geom.w = ((area.w - (cc->bwidth * 2)) * Conf.vtile) / 100;
 	cc->geom.h = area.h - (cc->bwidth * 2);
 	client_resize(cc, 1);
-	client_ptr_warp(cc);
 
 	mw = cc->geom.w + (cc->bwidth * 2);
 	y = area.y;
@@ -1046,4 +1090,14 @@ client_vtile(struct client_ctx *cc)
 		i++;
 		client_resize(ci, 1);
 	}
+}
+
+void
+client_flush_enter_events(void)
+{
+	XEvent ev;
+
+	XSync(X_Dpy, False);
+	while (XCheckMaskEvent(X_Dpy, EnterWindowMask, &ev))
+		LOG_DEBUG3("Discarding stale EnterNotify event");
 }
Index: group.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/group.c,v
diff -u -p -u -p -r1.138 group.c
--- group.c	27 Jan 2022 18:45:10 -0000	1.138
+++ group.c	3 Jul 2026 11:59:39 -0000
@@ -61,7 +61,7 @@ group_hide(struct group_ctx *gc)
 			continue;
 		if (!(cc->flags & CLIENT_STICKY) &&
 		    !(cc->flags & CLIENT_HIDDEN))
-			client_hide(cc);
+			client_hide(cc, 0);
 	}
 }
 
@@ -76,7 +76,7 @@ group_show(struct group_ctx *gc)
 			continue;
 		if (!(cc->flags & CLIENT_STICKY) &&
 		     (cc->flags & CLIENT_HIDDEN))
-			client_show(cc);
+			client_show(cc, 0);
 	}
 	group_restack(gc);
 	group_set_active(gc);
@@ -158,7 +158,7 @@ group_movetogroup(struct client_ctx *cc,
 			if (cc->gc == gc)
 				return;
 			if (gc->num != 0 && group_holds_only_hidden(gc))
-				client_hide(cc);
+				client_hide(cc, 0);
 			group_assign(gc, cc);
 		}
 	}
Index: kbfunc.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/kbfunc.c,v
diff -u -p -u -p -r1.176 kbfunc.c
--- kbfunc.c	20 Aug 2025 23:44:06 -0000	1.176
+++ kbfunc.c	3 Jul 2026 11:59:39 -0000
@@ -154,6 +154,11 @@ kbfunc_client_move_mb(void *ctx, struct 
 	struct screen_ctx	*sc = cc->sc;
 	struct geom		 area;
 	int			 move = 1;
+	int			 x, y;
+
+	xu_ptr_get(cc->win, &x, &y);
+	if (!client_inbound(cc, x, y))
+		return;
 
 	client_raise(cc);
 
@@ -242,6 +247,11 @@ kbfunc_client_resize_mb(void *ctx, struc
 	Time			 ltime = 0;
 	struct screen_ctx	*sc = cc->sc;
 	int			 resize = 1;
+	int			 x, y;
+
+	xu_ptr_get(cc->win, &x, &y);
+	if (!client_inbound(cc, x, y))
+		return;
 
 	if (cc->flags & CLIENT_FREEZE)
 		return;
@@ -329,7 +339,7 @@ kbfunc_client_snap(void *ctx, struct car
 		}
 	}
 	client_move(cc);
-	client_ptr_inbound(cc, 1);
+	client_flush_enter_events();
 }
 
 void
@@ -354,7 +364,7 @@ kbfunc_client_raise(void *ctx, struct ca
 void
 kbfunc_client_hide(void *ctx, struct cargs *cargs)
 {
-	client_hide(ctx);
+	client_hide(ctx, 1);
 }
 
 void
@@ -409,7 +419,7 @@ void
 kbfunc_client_cycle(void *ctx, struct cargs *cargs)
 {
 	struct screen_ctx	*sc = ctx;
-	struct client_ctx	*newcc, *oldcc, *prevcc;
+	struct client_ctx	*newcc, *oldcc;
 	int			 again = 1, flags = cargs->flag;
 
 	/* For X apps that ignore/steal events. */
@@ -420,7 +430,6 @@ kbfunc_client_cycle(void *ctx, struct ca
 	if (TAILQ_EMPTY(&sc->clientq))
 		return;
 
-	prevcc = TAILQ_FIRST(&sc->clientq);
 	oldcc = client_current(sc);
 	if (oldcc == NULL)
 		oldcc = (flags & CWM_CYCLE_REVERSE) ?
@@ -450,24 +459,17 @@ kbfunc_client_cycle(void *ctx, struct ca
 		}
 	}
 
-	/* Reset when cycling mod is released. XXX I hate this hack */
+	/* Reset when cycling mod is released */
 	sc->cycling = 1;
-	client_ptr_save(oldcc);
-	client_raise(prevcc);
-	client_raise(newcc);
-	if (!client_inbound(newcc, newcc->ptr.x, newcc->ptr.y)) {
-		newcc->ptr.x = newcc->geom.w / 2;
-		newcc->ptr.y = newcc->geom.h / 2;
-	}
 
-	/* When no client is active, warp pointer to last active. */
-	if (oldcc->flags & (CLIENT_ACTIVE))
-		client_ptr_warp(newcc);
-	else if (oldcc->flags & (CLIENT_SKIP_CYCLE))
-		client_ptr_warp(newcc);
-	else {
+	/* When no client is active, activate last active. */
+	if (oldcc->flags & (CLIENT_ACTIVE) ||
+	    oldcc->flags & (CLIENT_SKIP_CYCLE)) {
+		client_raise(newcc);
+		client_set_active(newcc);
+	} else {
 		client_raise(oldcc);
-		client_ptr_warp(oldcc);
+		client_set_active(oldcc);
 	}
 }
 
@@ -550,10 +552,10 @@ kbfunc_menu_client(void *ctx, struct car
 	if ((mi = menu_filter(sc, &menuq, "window", NULL, mflags,
 	    search_match_client, search_print_client)) != NULL) {
 		cc = (struct client_ctx *)mi->ctx;
-		client_show(cc);
+		client_show(cc, 1);
 		if ((old_cc = client_current(sc)) != NULL)
 			client_ptr_save(old_cc);
-		client_ptr_warp(cc);
+		client_set_active(cc);
 	}
 
 	menuq_clear(&menuq);
Index: screen.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/screen.c,v
diff -u -p -u -p -r1.98 screen.c
--- screen.c	27 Jan 2022 18:45:10 -0000	1.98
+++ screen.c	3 Jul 2026 11:59:39 -0000
@@ -87,22 +87,52 @@ screen_init(int which)
 static void
 screen_scan(struct screen_ctx *sc)
 {
-	struct client_ctx	 *cc, *active = NULL;
-	Window			*wins, w0, w1, rwin, cwin;
-	unsigned int		 nwins, i, mask;
-	int			 rx, ry, wx, wy;
+	struct client_ctx	*active = NULL, *cc;
+	Atom			 actual_type;
+	Atom			 net_active_win;
+	int			 actual_format, rx, ry, wx, wy;
+	unsigned int		 i, mask, nwins;
+	unsigned long		 bytes_after, nitems;
+	unsigned char		*prop_return = NULL;
+	Window			*wins, cwin, ewmh_active_win = None;
+	Window			 rwin, w0, w1;
 
+	/* Try to get the window active before the restart via EWMH */
+	net_active_win = XInternAtom(X_Dpy, "_NET_ACTIVE_WINDOW", False);
+
+	if (XGetWindowProperty(X_Dpy, sc->rootwin, net_active_win, 0, 1,
+	    False, XA_WINDOW, &actual_type, &actual_format, &nitems,
+	    &bytes_after, &prop_return) == Success && prop_return != NULL) {
+		if (nitems > 0)
+			ewmh_active_win = *(Window *)prop_return;
+		XFree(prop_return);
+	}
+
+	/* Query the current pointer position as a fallback */
 	XQueryPointer(X_Dpy, sc->rootwin, &rwin, &cwin,
 	    &rx, &ry, &wx, &wy, &mask);
 
+	/* Scan the window tree and initialize clients */
 	if (XQueryTree(X_Dpy, sc->rootwin, &w0, &w1, &wins, &nwins)) {
 		for (i = 0; i < nwins; i++) {
-			if ((cc = client_init(wins[i], sc)) != NULL)
-				if (cc->win == cwin)
-					active = cc;
+			if ((cc = client_init(wins[i], sc)) != NULL) {
+				/*
+				 * If EWMH told us which window was active,
+				 * match against that.  Otherwise, fall back
+				 * to the window under the pointer.
+				 */
+				if (ewmh_active_win != None) {
+					if (cc->win == ewmh_active_win)
+						active = cc;
+				} else {
+					if (cc->win == cwin)
+						active = cc;
+				}
+			}
 		}
 		XFree(wins);
 	}
+
 	if (active)
 		client_set_active(active);
 }
Index: xevents.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/xevents.c,v
diff -u -p -u -p -r1.151 xevents.c
--- xevents.c	5 Jan 2026 14:46:59 -0000	1.151
+++ xevents.c	3 Jul 2026 11:59:39 -0000
@@ -93,7 +93,7 @@ xev_handle_maprequest(XEvent *ee)
 		cc = client_init(e->window, NULL);
 
 	if ((cc != NULL) && (!(cc->flags & CLIENT_IGNORE)))
-		client_ptr_warp(cc);
+		client_set_active(cc);
 }
 
 static void
@@ -330,8 +330,8 @@ xev_handle_keypress(XEvent *ee)
 	kb->cargs->xev = CWM_XEV_KEY;
 	switch (kb->context) {
 	case CWM_CONTEXT_CC:
-		if (((cc = client_find(e->subwindow)) == NULL) &&
-		    ((cc = client_current(sc)) == NULL))
+		if (((cc = client_current(sc)) == NULL) &&
+		    ((cc = client_find(e->subwindow)) == NULL))
 			return;
 		(*kb->callback)(cc, kb->cargs);
 		break;
@@ -393,7 +393,7 @@ xev_handle_clientmessage(XEvent *ee)
 	if (e->message_type == cwmh[WM_CHANGE_STATE]) {
 		if ((cc = client_find(e->window)) != NULL) {
 	    		if (e->data.l[0] == IconicState)
-				client_hide(cc);
+				client_hide(cc, 1);
 		}
 	} else if (e->message_type == ewmh[_NET_CLOSE_WINDOW]) {
 		if ((cc = client_find(e->window)) != NULL) {
@@ -403,8 +403,7 @@ xev_handle_clientmessage(XEvent *ee)
 		if ((cc = client_find(e->window)) != NULL) {
 			if ((old_cc = client_current(NULL)) != NULL)
 				client_ptr_save(old_cc);
-			client_show(cc);
-			client_ptr_warp(cc);
+			client_show(cc, 1);
 		}
 	} else if (e->message_type == ewmh[_NET_WM_DESKTOP]) {
 		if ((cc = client_find(e->window)) != NULL) {


-- 
Walter