Fix two crashes

One was where WM was assuming that a seat existed during first manage,
but that's not always true, so we have to check that before running the
initialization code. I also split that off into its own function like in
Window.

The other crash was when trying to calculate the layout with the
output's width and/or height equal to zero, it would crash subtracting
the border width.

I discovered both of these when try to restart beansprout without
restarting River.
This commit is contained in:
Ben Buhse 2026-02-18 15:30:05 -06:00
commit 08be768d99
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
2 changed files with 76 additions and 56 deletions

View file

@ -576,8 +576,11 @@ pub fn manage(output: *Output) void {
} }
} }
// Calculate layout before managing windows // Calculate layout before managing windows, but only if output dimensions are initialized
if (output.usable_geometry.width > 0 and output.usable_geometry.height > 0) {
output.calculateLayout(); output.calculateLayout();
}
var it = output.windows.iterator(.forward); var it = output.windows.iterator(.forward);
while (it.next()) |window| { while (it.next()) |window| {
window.manage(); window.manage();
@ -612,6 +615,8 @@ pub fn render(output: *Output) void {
/// - Single window: maximized /// - Single window: maximized
/// - Multiple windows: stack (45% left, vertically tiled), primary (55% right) /// - Multiple windows: stack (45% left, vertically tiled), primary (55% right)
fn calculateLayout(output: *Output) void { fn calculateLayout(output: *Output) void {
// Shouldn't be called if height/width are not positive
assert(output.geometry.width > 0 and output.geometry.height > 0);
// Get a list of active windows // Get a list of active windows
var active_list: DoublyLinkedList = .{}; var active_list: DoublyLinkedList = .{};
var active_count: u31 = 0; var active_count: u31 = 0;
@ -714,8 +719,14 @@ fn calculateLayout(output: *Output) void {
} }
// Make space for borders // Make space for borders
if (window.pending_manage.dimensions.?.height > 2 * border_width and
window.pending_manage.dimensions.?.width > 2 * border_width)
{
window.pending_manage.dimensions.?.height -= 2 * border_width; window.pending_manage.dimensions.?.height -= 2 * border_width;
window.pending_manage.dimensions.?.width -= 2 * border_width; window.pending_manage.dimensions.?.width -= 2 * border_width;
} else {
log.warn("Can't add borders to some window; {s}'s dimensions are too small.", .{output.name orelse "some output"});
}
window.pending_render.position.?.x += border_width; window.pending_render.position.?.x += border_width;
window.pending_render.position.?.y += border_width; window.pending_render.position.?.y += border_width;
} }

View file

@ -72,21 +72,17 @@ pub fn prevOutput(wm: *WindowManager, current: *Output) ?*Output {
return @fieldParentPtr("link", prev_link); return @fieldParentPtr("link", prev_link);
} }
fn manage_start(wm: *WindowManager) void { fn initialize(wm: *WindowManager) void {
const river_window_manager_v1 = wm.river_window_manager_v1; if (wm.context.initialized) return;
const context = wm.context;
// This gets used shortly, so it goes at the beginning // We need a seat to intitialize this stuff, so let's just not do it right now.
context.manage(); // The WM can run fine without it, though, it won't be fully usuable.
const seat = wm.seats.first() orelse return;
if (!context.initialized) {
// This code runs during initial startup and after config reloads.
@branchHint(.cold);
context.initialized = true;
const seat = wm.seats.first() orelse @panic("Failed to get seat");
const river_seat_v1 = seat.river_seat_v1; const river_seat_v1 = seat.river_seat_v1;
const context = wm.context;
context.initialized = true;
// Tag bindings // Tag bindings
for (context.config.tag_binds.items) |tag_bind| { for (context.config.tag_binds.items) |tag_bind| {
comptime var i: u8 = 1; comptime var i: u8 = 1;
@ -132,6 +128,19 @@ fn manage_start(wm: *WindowManager) void {
} }
} }
fn manage(wm: *WindowManager) void {
const river_window_manager_v1 = wm.river_window_manager_v1;
const context = wm.context;
// This gets used shortly, so it goes at the beginning
context.manage();
if (!context.initialized) {
// This code runs during initial startup and after config reloads.
@branchHint(.cold);
wm.initialize();
}
{ {
var it = wm.outputs.iterator(.forward); var it = wm.outputs.iterator(.forward);
while (it.next()) |output| { while (it.next()) |output| {
@ -147,7 +156,7 @@ fn manage_start(wm: *WindowManager) void {
river_window_manager_v1.manageFinish(); river_window_manager_v1.manageFinish();
} }
fn render_start(wm: *WindowManager) void { fn render(wm: *WindowManager) void {
const river_window_manager_v1 = wm.river_window_manager_v1; const river_window_manager_v1 = wm.river_window_manager_v1;
{ {
var it = wm.seats.iterator(.forward); var it = wm.seats.iterator(.forward);
@ -171,8 +180,8 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
.unavailable => { .unavailable => {
fatal("Window manager unavailable (some other wm instance is running). Exiting", .{}); fatal("Window manager unavailable (some other wm instance is running). Exiting", .{});
}, },
.manage_start => wm.manage_start(), .manage_start => wm.manage(),
.render_start => wm.render_start(), .render_start => wm.render(),
.output => |ev| { .output => |ev| {
const output = Output.create(context, ev.id) catch @panic("Out of memory"); const output = Output.create(context, ev.id) catch @panic("Out of memory");
wm.outputs.append(output); wm.outputs.append(output);