From ed72d8a15d00ecb8c8a365512d6b718e1572c23a Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Wed, 1 Apr 2026 17:51:51 -0500 Subject: [PATCH] Fix crash when changing focused output With output_focus_follows_pointer=true, moving the pointer to a different output would update Seat's "focused" output, but not the actual focused window. If you then tried to changed focused outputs with keybinds, the assertion in XkbBindings.focusOutput() would fail. This commit also adds support for unfocusing a window on a pointer_leave event. Reported-by: Badacko Fixes: #7 --- docs/TODO.md | 3 ++- src/Seat.zig | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 6ab257d..80b0c81 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,7 +1,8 @@ # TODOs -These are in rough order of my priority, though no promises I do them in this order. +These are in no particular order anymore. +- [ ] Save focused window when switching back and forth between outputs via keybind - [ ] Add a config for how to focus when a window opens with a rule on another tag; should we switch tags, add tags, or just ignore it? - [ ] Support per-output single window ratio (in config; this already works at runtime) - [ ] Support per-output wallpapers diff --git a/src/Seat.zig b/src/Seat.zig index ea9455f..2f9ee8c 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -101,6 +101,13 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * .pointer_enter => |ev| if (seat.context.config.focus_follows_pointer) { seat.setWindowFocus(ev.window); }, + .pointer_leave => if (seat.context.config.focus_follows_pointer) { + if (seat.pending_manage.window == null) { + // We only want to clear the pending focus if there's not already one set, + // e.g. from a .pointer_enter event. + seat.pending_manage.window = .clear_focus; + } + }, .window_interaction => |ev| seat.setWindowFocus(ev.window), .op_delta => |ev| { seat.pending_manage.op_delta = .{ .dx = ev.dx, .dy = ev.dy }; @@ -111,21 +118,27 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * .pointer_position => |ev| { seat.pointer_pos.x = ev.x; seat.pointer_pos.y = ev.y; - if (seat.context.config.output_focus_follows_pointer) { - // Iterate over every display and check if the curser is inside it + + // We use this to track if the cursor is moving to a new output + if (seat.context.config.output_focus_follows_pointer) blk: { + const focused_output = seat.focused_output orelse break :blk; + if (utils.isPosInRect(seat.pointer_pos, focused_output.geometry)) { + // Same output, skip + break :blk; + } + + // New output, find which one the pointer is in var it = seat.context.wm.outputs.iterator(.forward); while (it.next()) |output| { - if (utils.isPosInRect(seat.pointer_pos, output.geometry)) { + if (output != focused_output and + utils.isPosInRect(seat.pointer_pos, output.geometry)) + { seat.focused_output = output; - break; } } } }, - - else => |ev| { - log.debug("unhandled event: {s}", .{@tagName(ev)}); - }, + else => {}, } } @@ -156,12 +169,6 @@ pub fn manage(seat: *Seat) void { if (focused != window) { // Tell the previously focused Window that it's no longer focused focused.pending_render.focused = false; - // Update the Bar to have the newly-focused window's title - if (focused.output) |output| { - if (output.bar) |*bar| { - bar.pending_render.draw = true; - } - } } } seat.focused_window = window; @@ -177,6 +184,13 @@ pub fn manage(seat: *Seat) void { seat.river_seat_v1.clearFocus(); }, } + // Bars should redraw because the title (may) have changed + var it = seat.context.wm.outputs.iterator(.forward); + while (it.next()) |output| { + if (output.bar) |*bar| { + bar.pending_render.draw = true; + } + } } } if (seat.pending_manage.output) |pending_output| {