Handle finished events in WM and IM

I was silently ignoring these before, which wasn't bad, but mostly meant
we wouldn't close them if the compositor ever finished. For WM, we just
send SIGINT and exit the WM in main(). For IM, we destroy() and clean up
like we do in XkbConfig.
This commit is contained in:
Ben Buhse 2026-03-17 20:10:20 -05:00
commit 52785078b7
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
2 changed files with 26 additions and 25 deletions

View file

@ -6,8 +6,8 @@ const InputManager = @This();
context: *Context, context: *Context,
river_input_manager_v1: *river.InputManagerV1, river_input_manager_v1: ?*river.InputManagerV1,
river_libinput_config_v1: *river.LibinputConfigV1, river_libinput_config_v1: ?*river.LibinputConfigV1,
/// All input devices that we've been advertised /// All input devices that we've been advertised
input_devices: wl.list.Head(InputDevice, .link), input_devices: wl.list.Head(InputDevice, .link),
@ -31,58 +31,54 @@ pub fn create(context: *Context, river_input_manager_v1: *river.InputManagerV1,
im.input_devices.init(); im.input_devices.init();
im.libinput_devices.init(); im.libinput_devices.init();
im.river_input_manager_v1.setListener(*InputManager, inputManagerV1Listener, im); river_input_manager_v1.setListener(*InputManager, inputManagerV1Listener, im);
im.river_libinput_config_v1.setListener(*InputManager, libinputConfigV1Listener, im); river_libinput_config_v1.setListener(*InputManager, libinputConfigV1Listener, im);
return im; return im;
} }
pub fn destroy(im: *InputManager) void { pub fn destroy(im: *InputManager) void {
{ {
var it = im.input_devices.iterator(.forward); var it = im.input_devices.safeIterator(.forward);
while (it.next()) |input_device| { while (it.next()) |input_device| input_device.destroy();
input_device.destroy();
}
} }
{ {
var it = im.libinput_devices.iterator(.forward); var it = im.libinput_devices.safeIterator(.forward);
while (it.next()) |libinput_device| { while (it.next()) |libinput_device| libinput_device.destroy();
libinput_device.destroy();
} }
if (im.river_input_manager_v1) |river_input_manager_v1| {
river_input_manager_v1.destroy();
}
if (im.river_libinput_config_v1) |river_libinput_config_v1| {
river_libinput_config_v1.destroy();
} }
im.river_input_manager_v1.destroy();
im.river_libinput_config_v1.destroy();
utils.gpa.destroy(im); utils.gpa.destroy(im);
} }
pub fn inputManagerV1Listener(river_input_manager_v1: *river.InputManagerV1, event: river.InputManagerV1.Event, im: *InputManager) void { pub fn inputManagerV1Listener(river_input_manager_v1: *river.InputManagerV1, event: river.InputManagerV1.Event, im: *InputManager) void {
assert(im.river_input_manager_v1 == river_input_manager_v1); assert(im.river_input_manager_v1.? == river_input_manager_v1);
switch (event) { switch (event) {
.input_device => |ev| { .input_device => |ev| {
const input_device = InputDevice.create(ev.id) catch @panic("Out of memory"); const input_device = InputDevice.create(ev.id) catch @panic("Out of memory");
im.input_devices.append(input_device); im.input_devices.append(input_device);
}, },
.finished => { .finished => {
// TODO: Should call destroy on the river_input_manager_v1 and on this device, river_input_manager_v1.destroy();
// but might need to make the globals optional so that we know when we can destroy this im.river_input_manager_v1 = null;
// object.
log.debug("unhandled event: finished", .{});
}, },
} }
} }
pub fn libinputConfigV1Listener(river_libinput_config_v1: *river.LibinputConfigV1, event: river.LibinputConfigV1.Event, im: *InputManager) void { pub fn libinputConfigV1Listener(river_libinput_config_v1: *river.LibinputConfigV1, event: river.LibinputConfigV1.Event, im: *InputManager) void {
assert(im.river_libinput_config_v1 == river_libinput_config_v1); assert(im.river_libinput_config_v1.? == river_libinput_config_v1);
switch (event) { switch (event) {
.libinput_device => |ev| { .libinput_device => |ev| {
const libinput_device = LibinputDevice.create(im.context, ev.id) catch @panic("Out of memory"); const libinput_device = LibinputDevice.create(im.context, ev.id) catch @panic("Out of memory");
im.libinput_devices.append(libinput_device); im.libinput_devices.append(libinput_device);
}, },
.finished => { .finished => {
// TODO: Should call destroy on the river_libinput_config_v1 and on this device, river_libinput_config_v1.destroy();
// but might need to make the globals optional so that we know when we can destroy this im.river_libinput_config_v1 = null;
// object.
log.debug("unhandled event: finished", .{});
}, },
} }
} }

View file

@ -284,6 +284,10 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
s.pending_manage.should_warp_pointer = true; s.pending_manage.should_warp_pointer = true;
} }
}, },
.finished => {
log.info("river_window_manager_v1 finished, exiting.", .{});
posix.raise(posix.SIG.INT) catch |err| log.err("Failed to raise SIGINT: {}", .{err});
},
else => |ev| { else => |ev| {
log.debug("unhandled event: {s}", .{@tagName(ev)}); log.debug("unhandled event: {s}", .{@tagName(ev)});
}, },
@ -293,6 +297,7 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const fatal = std.process.fatal; const fatal = std.process.fatal;
const posix = std.posix;
const wayland = @import("wayland"); const wayland = @import("wayland");
const wl = wayland.client.wl; const wl = wayland.client.wl;