foxygit / dotfiles Log in
commit f9e2bed1e21546c8fd59dd075a703045e2d6d7e0
Author:     mrfox <jens.se@icloud.com>
AuthorDate: Tue Sep 22 21:22:22 2026 +0200
Commit:     mrfox <jens.se@icloud.com>
CommitDate: Tue Sep 22 21:22:22 2026 +0200

    sxwm: align focus-follows-mouse with dwm's exact reference pattern

    Compared against dwm's own manage()/enternotify() (sxwm is itself
    dwm-derived): same client-level EnterWindowMask, same mode/detail
    filter, resolving the entered client from crossing_ev->window directly.
    Dropped the earlier root-window/->subwindow detour -- root never
    receives a crossing event for sibling-to-sibling transitions (the LCA
    is excluded from notification per the X11 crossing-event rules when
    both windows are unrelated), so it couldn't have worked for the
    tiled-window-to-tiled-window case that matters most.

    Live-tested across several sxwm restarts on one long, heavily-churned
    session and could not get it working: a fresh client window's delivered
    event mask was missing EnterWindowMask moments after sxwm's own
    XSelectInput call requested it, confirmed via a direct
    XGetWindowAttributes readback from inside sxwm itself right after the
    call, which shouldn't be possible per the X11 protocol (a client's own
    XSelectInput deterministically sets its own contribution to the mask).
    Root cause not found. Since the implementation now matches dwm's
    verified-working reference exactly, the leading theory is session-
    specific interference from today's extensive testing (many sxwm
    restarts, hundreds of test windows) rather than a bug in this patch --
    flagged in the README as unconfirmed pending a retest after a clean
    logout/login.

    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 README.md                 |  2 +-
 focus-follows-mouse.patch | 20 ++++++++++++--------
 2 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index ad25260..6965813 100644
--- a/README.md
+++ b/README.md
@@ -198,7 +198,7 @@ order:
 | [`monitor-hotplug-remap.patch`](monitor-hotplug-remap.patch) | Windows lost their correct monitor association on hotplug (e.g. `lid-display-watch` toggling eDP); monitor ownership is now recomputed from each window's actual position |
 | [`fullscreen-monitor-fix.patch`](fullscreen-monitor-fix.patch) | Fullscreening a window that had been moved to another monitor by an external tool (`xdotool`, `screensaver-launch`) fullscreened it on the wrong screen; the target monitor is now recomputed from the window's real geometry |
 | [`strut-fresh-screen-size.patch`](strut-fresh-screen-size.patch) | A bottom/right-edge panel (e.g. sxbar's `secondary_bar`) could end up hidden behind normal windows — `reserve_bottom`/`reserve_right` are measured from the *opposite* screen edge and need the full screen size, but read it from `scr_width`/`scr_height`, cached globals only refreshed by `update_mons()`; `reserve_top`/`reserve_left` never depend on screen size so stayed correct. Reads the screen size fresh from the X server instead |
-| [`focus-follows-mouse.patch`](focus-follows-mouse.patch) | sxwm selects `EnterWindowMask` on every client already but never had a handler for it — hovering a window never focused it, only clicking did. Adds an `EnterNotify` handler behind a new `focus_follows_mouse` config key (opt-in, off by default upstream; this repo's `sxwmrc` turns it on). Only changes input focus — doesn't raise the window or warp the cursor, matching classic sloppy-focus behavior |
+| [`focus-follows-mouse.patch`](focus-follows-mouse.patch) | sxwm selects `EnterWindowMask` on every client already but never had a handler for it — hovering a window never focused it, only clicking did. Adds an `EnterNotify` handler behind a new `focus_follows_mouse` config key (opt-in, off by default upstream; this repo's `sxwmrc` turns it on), matching dwm's `manage()`/`enternotify()` pattern exactly (same event mask, same `mode`/`detail` filter, resolving the entered client straight from `crossing_ev->window`) since sxwm is itself dwm-derived. Only changes input focus — doesn't raise the window or warp the cursor, matching classic sloppy-focus behavior. Applies cleanly and builds clean against the other 6 local patches; **not yet confirmed working live** — one test session, after several `sxwm` restarts, saw the client's delivered event mask silently missing `EnterWindowMask` moments after sxwm's own `XSelectInput` call requested it (verified via direct `XGetWindowAttributes` readback), for reasons not root-caused. Retest after a full logout/login before trusting this patch |

 ## dmenu patches

diff --git a/focus-follows-mouse.patch b/focus-follows-mouse.patch
index 16a0cc2..79e7fa5 100644
--- a/focus-follows-mouse.patch
+++ b/focus-follows-mouse.patch
@@ -24,7 +24,7 @@ index e50db00..ed09899 100644
  			cfg->border_foc_col = parse_col(rest);
  		else if (!strcmp(key, "gaps"))
 diff --git a/src/sxwm.c b/src/sxwm.c
-index c30db92..d30d023 100644
+index c30db92..6f2f934 100644
 --- a/src/sxwm.c
 +++ b/src/sxwm.c
 @@ -64,6 +64,7 @@ void hdl_config_ntf(XEvent *xev);
@@ -35,7 +35,7 @@ index c30db92..d30d023 100644
  void hdl_keypress(XEvent *xev);
  void hdl_mapping_ntf(XEvent *xev);
  void hdl_map_req(XEvent *xev);
-@@ -1174,6 +1175,27 @@ void hdl_destroy_ntf(XEvent *xev)
+@@ -1174,6 +1175,31 @@ void hdl_destroy_ntf(XEvent *xev)
  	}
  }

@@ -46,10 +46,14 @@ index c30db92..d30d023 100644
 +	if (!user_config.focus_follows_mouse)
 +		return;
 +
-+	/* Ignore crossings from a pointer grab (drag/resize in progress) and
-+	 * crossings into a child of the window we're already in -- both fire
-+	 * EnterNotify without the pointer actually landing on a new client,
-+	 * and would otherwise flicker focus or fight an active drag. */
++	/* Same filter dwm uses (manage()'s XSelectInput + enternotify()):
++	 * mode != NotifyNormal skips crossings synthesized by an active
++	 * pointer grab (drag/resize), detail == NotifyInferior skips crossing
++	 * into a child of a window we're already in -- both would otherwise
++	 * flicker focus or fight an active drag. add_client()'s window_masks
++	 * already requests EnterWindowMask per client (same as dwm's
++	 * manage()), so crossing_ev->window here is directly the client
++	 * being entered -- no need for ->subwindow. */
 +	if (crossing_ev->mode != NotifyNormal || crossing_ev->detail == NotifyInferior)
 +		return;
 +
@@ -63,7 +67,7 @@ index c30db92..d30d023 100644
  void hdl_keypress(XEvent *xev)
  {
  	KeyCode code = xev->xkey.keycode;
-@@ -1627,6 +1649,7 @@ void init_defaults(void)
+@@ -1627,6 +1653,7 @@ void init_defaults(void)
  	user_config.n_binds = 0;
  	user_config.new_win_focus = True;
  	user_config.warp_cursor = True;
@@ -71,7 +75,7 @@ index c30db92..d30d023 100644
  	user_config.new_win_master = False;
  	user_config.floating_on_top = True;
  }
-@@ -2301,6 +2324,7 @@ void setup(void)
+@@ -2301,6 +2328,7 @@ void setup(void)
  	evtable[ConfigureNotify] = hdl_config_ntf;
  	evtable[ConfigureRequest] = hdl_config_req;
  	evtable[DestroyNotify] = hdl_destroy_ntf;