commit ed28fa2f1a024805b4345e98cadbe4033626efee
Author: mrfox <jens.se@icloud.com>
AuthorDate: Tue Sep 22 19:40:23 2026 +0200
Commit: mrfox <jens.se@icloud.com>
CommitDate: Tue Sep 22 19:40:23 2026 +0200
sxwm: add focus-follows-mouse (hover to focus)
sxwm already selects EnterWindowMask on every client but never had a
handler for it -- hovering a window only changed the cursor, not focus;
only clicking did. Added an EnterNotify handler behind a new opt-in
focus_follows_mouse config key (defaults to off upstream; this repo's
sxwmrc turns it on).
Ignores crossings from an active pointer grab (mode != NotifyNormal, so
mid-drag/resize doesn't fight focus) and crossings into a child of the
already-focused window (detail == NotifyInferior, the standard sloppy-
focus guard against flicker). Only changes input focus -- doesn't raise
the window or warp the cursor (the mouse is already there).
Verified: applies cleanly on top of the existing 6 local sxwm patches
and builds clean with -Wall -Wextra. Not yet installed live -- swapping
the running window manager needs the user to do it (sudo, and it's
disruptive to test blind).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
README.md | 3 +-
config/sxwmrc | 1 +
focus-follows-mouse.patch | 81 +++++++++++++++++++++++++++++++++++++++++++++++
install.sh | 7 ++++
4 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 107c24e..ad25260 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ and you have a working environment.
| | |
|---|---|
-| 🪟 | **sxwm** — tiling window manager, patched with 5 local fixes (DPI, taskbar focus, multi-monitor struts, hotplug remap, fullscreen targeting) |
+| 🪟 | **sxwm** — tiling window manager, patched with 6 local fixes/additions (DPI, taskbar focus, multi-monitor struts, hotplug remap, fullscreen targeting, focus-follows-mouse) |
| 📊 | **sxbar** — status bar with clock, date, battery, volume, CPU, brightness, Bluetooth, network, media player, taskbar and start menu popups |
| 🚀 | **dmenu** — fast application launcher (`mod + p`), centered on screen with results listed vertically |
| 📶 | **bluetui** — Bluetooth management right in the terminal |
@@ -198,6 +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 |
## dmenu patches
diff --git a/config/sxwmrc b/config/sxwmrc
index ec94c76..36b5357 100644
--- a/config/sxwmrc
+++ b/config/sxwmrc
@@ -23,6 +23,7 @@ motion_throttle : 60 # Set to screen refresh rate for smoothest motions
should_float : "pcmanfm", "obs", "screensaver"
new_win_focus : true
warp_cursor : true
+focus_follows_mouse : true
floating_on_top : true
new_win_master : false
can_swallow : "st"
diff --git a/focus-follows-mouse.patch b/focus-follows-mouse.patch
new file mode 100644
index 0000000..16a0cc2
--- /dev/null
+++ b/focus-follows-mouse.patch
@@ -0,0 +1,81 @@
+diff --git a/src/defs.h b/src/defs.h
+index ac8f74f..a18731f 100644
+--- a/src/defs.h
++++ b/src/defs.h
+@@ -96,6 +96,7 @@ typedef struct {
+ int resize_window_amt;
+ Bool new_win_focus;
+ Bool warp_cursor;
++ Bool focus_follows_mouse;
+ Bool floating_on_top;
+ Bool new_win_master;
+ Binding binds[MAX_ITEMS];
+diff --git a/src/parser.c b/src/parser.c
+index e50db00..ed09899 100644
+--- a/src/parser.c
++++ b/src/parser.c
+@@ -372,6 +372,8 @@ int parser(Config *cfg)
+ }
+ else if (!strcmp(key, "floating_on_top"))
+ cfg->floating_on_top = !strcmp(rest, "true");
++ else if (!strcmp(key, "focus_follows_mouse"))
++ cfg->focus_follows_mouse = !strcmp(rest, "true");
+ else if (!strcmp(key, "focused_border_colour"))
+ 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
+--- a/src/sxwm.c
++++ b/src/sxwm.c
+@@ -64,6 +64,7 @@ void hdl_config_ntf(XEvent *xev);
+ void hdl_config_req(XEvent *xev);
+ void hdl_dummy(XEvent *xev);
+ void hdl_destroy_ntf(XEvent *xev);
++void hdl_enter_ntf(XEvent *xev);
+ 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)
+ }
+ }
+
++void hdl_enter_ntf(XEvent *xev)
++{
++ XCrossingEvent *crossing_ev = &xev->xcrossing;
++
++ 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. */
++ if (crossing_ev->mode != NotifyNormal || crossing_ev->detail == NotifyInferior)
++ return;
++
++ Client *c = find_client(find_toplevel(crossing_ev->window));
++ if (!c || c == focused || c->ws != current_ws)
++ return;
++
++ set_input_focus(c, False, False);
++}
++
+ void hdl_keypress(XEvent *xev)
+ {
+ KeyCode code = xev->xkey.keycode;
+@@ -1627,6 +1649,7 @@ void init_defaults(void)
+ user_config.n_binds = 0;
+ user_config.new_win_focus = True;
+ user_config.warp_cursor = True;
++ user_config.focus_follows_mouse = False;
+ user_config.new_win_master = False;
+ user_config.floating_on_top = True;
+ }
+@@ -2301,6 +2324,7 @@ void setup(void)
+ evtable[ConfigureNotify] = hdl_config_ntf;
+ evtable[ConfigureRequest] = hdl_config_req;
+ evtable[DestroyNotify] = hdl_destroy_ntf;
++ evtable[EnterNotify] = hdl_enter_ntf;
+ evtable[KeyPress] = hdl_keypress;
+ evtable[MappingNotify] = hdl_mapping_ntf;
+ evtable[MapRequest] = hdl_map_req;
diff --git a/install.sh b/install.sh
index cceb5e2..99c72eb 100755
--- a/install.sh
+++ b/install.sh
@@ -471,6 +471,13 @@ step_sxwm() {
# monitorns egen x/y, och påverkades inte). Läser bredd/höjd direkt från
# X-servern i update_struts() istället för att lita på cachen.
patch -p1 < "$SCRIPT_DIR/strut-fresh-screen-size.patch"
+ # sxwm never had focus-follows-mouse -- it selects EnterWindowMask on
+ # every client already but has no handler for it (defaults to a no-op),
+ # so hovering a window never focused it, only clicking did. Adds an
+ # EnterNotify handler behind a new opt-in "focus_follows_mouse" config
+ # key (default off); doesn't raise or warp the cursor, just changes
+ # input focus, matching classic sloppy-focus behavior.
+ patch -p1 < "$SCRIPT_DIR/focus-follows-mouse.patch"
make
sudo make install