git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit796bbb1526b4f0d3626bcb38527f4d34f5428b2d
parent30f271eae1
authorIsaac Freund <[email protected]>
date2025-07-05 20:32
build: update to wlroots 0.19

 README.md                          |  2 +-
 build.zig                          |  5 +++--
 build.zig.zon                      |  4 ++--
 river/Cursor.zig                   | 36 ++++++++++++++++++++++++++++++------
 river/Decoration.zig               |  4 +---
 river/DragIcon.zig                 |  2 +-
 river/InputDevice.zig              |  4 ++--
 river/InputManager.zig             |  4 ++--
 river/Keyboard.zig                 |  2 +-
 river/LockManager.zig              |  2 +-
 river/LockSurface.zig              | 10 +++++-----
 river/Output.zig                   | 27 ++++-----------------------
 river/OutputManager.zig            | 32 ++++++++++++--------------------
 river/PointerConstraint.zig        | 16 ++++++++--------
 river/SceneNodeData.zig            |  8 ++++----
 river/Seat.zig                     |  6 +++---
 river/Server.zig                   |  5 ++++-
 river/ShellSurface.zig             |  6 +++---
 river/TabletTool.zig               |  6 +++---
 river/TextInput.zig                | 10 +++++-----
 river/XdgDecoration.zig            |  6 +++---
 river/XdgToplevel.zig              | 14 +++++++-------
 river/XwaylandOverrideRedirect.zig |  4 ++--
 river/XwaylandWindow.zig           |  4 ++--
 24 files changed, 109 insertions(+), 110 deletions(-)

diff --git a/README.md b/README.md
index 41eb53d..d9f3aa3 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ distribution.
 - [zig](https://ziglang.org/download/) 0.14
 - wayland
 - wayland-protocols
-- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.18
+- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.19
 - xkbcommon
 - libevdev
 - pixman
diff --git a/build.zig b/build.zig
index 0c5ef6c..3d7e5da 100644
--- a/build.zig
+++ b/build.zig
@@ -143,7 +143,8 @@ pub fn build(b: *Build) !void {
     // exposed to the wlroots module for @cImport() to work. This seems to be
     // the best way to do so with the current std.Build API.
     wlroots.resolved_target = target;
-    wlroots.linkSystemLibrary("wlroots-0.18", .{});
+    const wlroots_pkgconf = "wlroots-0.19";
+    wlroots.linkSystemLibrary(wlroots_pkgconf, .{});
 
     const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") });
     const globber = b.createModule(.{ .root_source_file = b.path("common/globber.zig") });
@@ -164,7 +165,7 @@ pub fn build(b: *Build) !void {
         river.linkSystemLibrary("libevdev");
         river.linkSystemLibrary("libinput");
         river.linkSystemLibrary("wayland-server");
-        river.linkSystemLibrary("wlroots-0.18");
+        river.linkSystemLibrary(wlroots_pkgconf);
         river.linkSystemLibrary("xkbcommon");
         river.linkSystemLibrary("pixman-1");
 
diff --git a/build.zig.zon b/build.zig.zon
index 228eda7..c341993 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -18,8 +18,8 @@
             .hash = "wayland-0.3.0-lQa1kjPIAQDmhGYpY-zxiRzQJFHQ2VqhJkQLbKKdt5wl",
         },
         .wlroots = .{
-            .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.18.2.tar.gz",
-            .hash = "wlroots-0.18.2-jmOlchnIAwBq45_cxU1V3OWErxxJjQZlc9PyJfR-l3uk",
+            .url = "https://codeberg.org/ifreund/zig-wlroots/archive/f92ba27133ecf702d85c9d3894f98a336389bbd9.tar.gz",
+            .hash = "wlroots-0.19.3-dev-jmOlcr7_AwClfjFwW8oOkWoqAbt9oPLqgdvfFYEXqlOF",
         },
         .xkbcommon = .{
             .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
diff --git a/river/Cursor.zig b/river/Cursor.zig
index f0aa0a8..3e21d2d 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -181,6 +181,30 @@ pub fn init(cursor: *Cursor, seat: *Seat) !void {
 }
 
 pub fn deinit(cursor: *Cursor) void {
+    cursor.axis.link.remove();
+    cursor.button.link.remove();
+    cursor.frame.link.remove();
+    cursor.motion_absolute.link.remove();
+    cursor.motion_relative.link.remove();
+    cursor.swipe_begin.link.remove();
+    cursor.swipe_update.link.remove();
+    cursor.swipe_end.link.remove();
+    cursor.pinch_begin.link.remove();
+    cursor.pinch_update.link.remove();
+    cursor.pinch_end.link.remove();
+    cursor.request_set_cursor.link.remove();
+
+    cursor.touch_down.link.remove();
+    cursor.touch_motion.link.remove();
+    cursor.touch_up.link.remove();
+    cursor.touch_cancel.link.remove();
+    cursor.touch_frame.link.remove();
+
+    cursor.tablet_tool_axis.link.remove();
+    cursor.tablet_tool_proximity.link.remove();
+    cursor.tablet_tool_tip.link.remove();
+    cursor.tablet_tool_button.link.remove();
+
     cursor.xcursor_manager.destroy();
     cursor.wlr_cursor.destroy();
     cursor.pressed.deinit(util.gpa);
@@ -480,7 +504,7 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
 }
 
 pub fn processAxis(cursor: *Cursor, event: *const wlr.Pointer.event.Axis) void {
-    const device: *InputDevice = @ptrFromInt(event.device.data);
+    const device: *InputDevice = @alignCast(@ptrCast(event.device.data));
     cursor.seat.wlr_seat.pointerNotifyAxis(
         event.time_msec,
         event.orientation,
@@ -612,7 +636,7 @@ fn handleTabletToolAxis(
     _: *wl.Listener(*wlr.Tablet.event.Axis),
     event: *wlr.Tablet.event.Axis,
 ) void {
-    const device: *InputDevice = @ptrFromInt(event.device.data);
+    const device: *InputDevice = @alignCast(@ptrCast(event.device.data));
     const tablet: *Tablet = @fieldParentPtr("device", device);
 
     device.seat.handleActivity();
@@ -626,7 +650,7 @@ fn handleTabletToolProximity(
     _: *wl.Listener(*wlr.Tablet.event.Proximity),
     event: *wlr.Tablet.event.Proximity,
 ) void {
-    const device: *InputDevice = @ptrFromInt(event.device.data);
+    const device: *InputDevice = @alignCast(@ptrCast(event.device.data));
     const tablet: *Tablet = @fieldParentPtr("device", device);
 
     device.seat.handleActivity();
@@ -640,7 +664,7 @@ fn handleTabletToolTip(
     _: *wl.Listener(*wlr.Tablet.event.Tip),
     event: *wlr.Tablet.event.Tip,
 ) void {
-    const device: *InputDevice = @ptrFromInt(event.device.data);
+    const device: *InputDevice = @alignCast(@ptrCast(event.device.data));
     const tablet: *Tablet = @fieldParentPtr("device", device);
 
     device.seat.handleActivity();
@@ -654,7 +678,7 @@ fn handleTabletToolButton(
     _: *wl.Listener(*wlr.Tablet.event.Button),
     event: *wlr.Tablet.event.Button,
 ) void {
-    const device: *InputDevice = @ptrFromInt(event.device.data);
+    const device: *InputDevice = @alignCast(@ptrCast(event.device.data));
     const tablet: *Tablet = @fieldParentPtr("device", device);
 
     device.seat.handleActivity();
@@ -707,7 +731,7 @@ fn passthrough(cursor: *Cursor, time: u32) void {
 fn updateDragIcons(cursor: *Cursor) void {
     var it = server.scene.drag_icons.children.iterator(.forward);
     while (it.next()) |node| {
-        const icon = @as(*DragIcon, @ptrFromInt(node.data));
+        const icon = @as(*DragIcon, @alignCast(@ptrCast(node.data)));
 
         if (icon.wlr_drag_icon.drag.seat == cursor.seat.wlr_seat) {
             icon.updatePosition(cursor);
diff --git a/river/Decoration.zig b/river/Decoration.zig
index ee2c14c..a47817d 100644
--- a/river/Decoration.zig
+++ b/river/Decoration.zig
@@ -145,9 +145,7 @@ fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
 }
 
 fn commit(wlr_surface: *wlr.Surface) callconv(.C) void {
-    if (wlr_surface.unmap_commit) {
-        wlr_surface.unmap();
-    } else if (wlr_surface.hasBuffer()) {
+    if (wlr_surface.hasBuffer()) {
         wlr_surface.map();
     }
 }
diff --git a/river/DragIcon.zig b/river/DragIcon.zig
index 2ff1152..9780891 100644
--- a/river/DragIcon.zig
+++ b/river/DragIcon.zig
@@ -42,7 +42,7 @@ pub fn create(wlr_drag_icon: *wlr.Drag.Icon, cursor: *Cursor) error{OutOfMemory}
         .wlr_drag_icon = wlr_drag_icon,
         .scene_drag_icon = scene_drag_icon,
     };
-    scene_drag_icon.node.data = @intFromPtr(drag_icon);
+    scene_drag_icon.node.data = drag_icon;
 
     drag_icon.updatePosition(cursor);
 
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index b0a106e..3c1c5e7 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -86,7 +86,7 @@ pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !vo
         .link = undefined,
     };
 
-    wlr_device.data = @intFromPtr(device);
+    wlr_device.data = device;
 
     wlr_device.events.destroy.add(&device.destroy);
 
@@ -117,7 +117,7 @@ pub fn deinit(device: *InputDevice) void {
         device.seat.updateCapabilities();
     }
 
-    device.wlr_device.data = 0;
+    device.wlr_device.data = null;
 
     device.* = undefined;
 }
diff --git a/river/InputManager.zig b/river/InputManager.zig
index bae8b77..343cd7d 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -176,7 +176,7 @@ fn handleNewVirtualKeyboard(
     _: *wl.Listener(*wlr.VirtualKeyboardV1),
     virtual_keyboard: *wlr.VirtualKeyboardV1,
 ) void {
-    const seat: *Seat = @ptrFromInt(virtual_keyboard.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(virtual_keyboard.seat.data));
     seat.addDevice(&virtual_keyboard.keyboard.base);
 }
 
@@ -191,7 +191,7 @@ fn handleNewConstraint(
 }
 
 fn handleNewInputMethod(_: *wl.Listener(*wlr.InputMethodV2), input_method: *wlr.InputMethodV2) void {
-    const seat: *Seat = @ptrFromInt(input_method.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(input_method.seat.data));
 
     log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
 
diff --git a/river/Keyboard.zig b/river/Keyboard.zig
index 8a54cac..d36214c 100644
--- a/river/Keyboard.zig
+++ b/river/Keyboard.zig
@@ -104,7 +104,7 @@ pub fn init(keyboard: *Keyboard, seat: *Seat, wlr_device: *wlr.InputDevice) !voi
     errdefer keyboard.device.deinit();
 
     const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
-    wlr_keyboard.data = @intFromPtr(keyboard);
+    wlr_keyboard.data = keyboard;
 
     // wlroots will log a more detailed error if this fails.
     if (!wlr_keyboard.setKeymap(server.config.keymap)) return error.OutOfMemory;
diff --git a/river/LockManager.zig b/river/LockManager.zig
index f777b14..5603668 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -243,7 +243,7 @@ pub fn lockSurfaceFromOutput(manager: *LockManager, output: *Output) ?*LockSurfa
 
     var it = lock.surfaces.iterator(.forward);
     while (it.next()) |wlr_lock_surface| {
-        const lock_surface: *LockSurface = @ptrFromInt(wlr_lock_surface.data);
+        const lock_surface: *LockSurface = @alignCast(@ptrCast(wlr_lock_surface.data));
         if (output == lock_surface.getOutput()) {
             return lock_surface;
         }
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index 716c774..0821db5 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -44,14 +44,14 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc
         .wlr_lock_surface = wlr_lock_surface,
         .lock = lock,
     };
-    wlr_lock_surface.data = @intFromPtr(lock_surface);
+    wlr_lock_surface.data = lock_surface;
 
     const tree = try server.scene.locked_tree.createSceneSubsurfaceTree(wlr_lock_surface.surface);
     errdefer tree.node.destroy();
 
     try SceneNodeData.attach(&tree.node, .{ .lock_surface = lock_surface });
 
-    wlr_lock_surface.surface.data = @intFromPtr(&tree.node);
+    wlr_lock_surface.surface.data = &tree.node;
 
     wlr_lock_surface.surface.events.map.add(&lock_surface.map);
     wlr_lock_surface.events.destroy.add(&lock_surface.surface_destroy);
@@ -64,7 +64,7 @@ pub fn destroy(lock_surface: *LockSurface) void {
         var surface_it = lock_surface.lock.surfaces.iterator(.forward);
         const new_focus: Seat.Focus = while (surface_it.next()) |surface| {
             if (surface != lock_surface.wlr_lock_surface)
-                break .{ .lock_surface = @ptrFromInt(surface.data) };
+                break .{ .lock_surface = @alignCast(@ptrCast(surface.data)) };
         } else .none;
 
         var seat_it = server.input_manager.seats.iterator(.forward);
@@ -84,13 +84,13 @@ pub fn destroy(lock_surface: *LockSurface) void {
     lock_surface.surface_destroy.link.remove();
 
     // The wlr_surface may outlive the wlr_lock_surface so we must clean up the user data.
-    lock_surface.wlr_lock_surface.surface.data = 0;
+    lock_surface.wlr_lock_surface.surface.data = null;
 
     util.gpa.destroy(lock_surface);
 }
 
 pub fn getOutput(lock_surface: *LockSurface) *Output {
-    return @ptrFromInt(lock_surface.wlr_lock_surface.output.data);
+    return @alignCast(@ptrCast(lock_surface.wlr_lock_surface.output.data));
 }
 
 pub fn configure(lock_surface: *LockSurface) void {
diff --git a/river/Output.zig b/river/Output.zig
index 9e27446..aea90d2 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -156,10 +156,6 @@ lock_render_state: enum {
     lock_surface,
 } = .blanked,
 
-/// Set to true if a gamma control client makes a set gamma request.
-/// This request is handled while rendering the next frame in handleFrame().
-gamma_dirty: bool = false,
-
 /// Root.outputs
 link: wl.list.Link,
 
@@ -206,7 +202,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
         .link = undefined,
         .link_sent = undefined,
     };
-    wlr_output.data = @intFromPtr(output);
+    wlr_output.data = output;
 
     server.om.outputs.append(output);
     output.link_sent.init();
@@ -238,7 +234,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
     output.frame.link.remove();
     output.present.link.remove();
 
-    wlr_output.data = 0;
+    wlr_output.data = null;
 
     output.wlr_output = null;
     output.scene_output = null;
@@ -386,7 +382,6 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) voi
 
     // TODO this should probably be retried on failure
     output.renderAndCommit() catch |err| switch (err) {
-        error.OutOfMemory => log.err("out of memory", .{}),
         error.CommitFailed => log.err("output commit failed for {s}", .{wlr_output.name}),
     };
 
@@ -395,6 +390,8 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) voi
 }
 
 fn renderAndCommit(output: *Output) !void {
+    if (!output.scene_output.?.needsFrame()) return;
+
     const wlr_output = output.wlr_output.?;
 
     var state = wlr.Output.State.init();
@@ -402,26 +399,10 @@ fn renderAndCommit(output: *Output) !void {
 
     output.current.applyNoModeset(&state);
 
-    if (output.gamma_dirty) {
-        const control = server.om.gamma_control_manager.getControl(wlr_output);
-        if (!wlr.GammaControlV1.apply(control, &state)) return error.OutOfMemory;
-
-        if (!wlr_output.testState(&state)) {
-            wlr.GammaControlV1.sendFailedAndDestroy(control);
-            state.clearGammaLut();
-            // If the backend does not support gamma LUTs it will reject any
-            // state with the gamma LUT committed bit set even if the state
-            // has a null LUT. The wayland backend for example has this behavior.
-            state.committed.gamma_lut = false;
-        }
-    }
-
     if (!output.scene_output.?.buildState(&state, null)) return error.CommitFailed;
 
     if (!wlr_output.commitState(&state)) return error.CommitFailed;
 
-    output.gamma_dirty = false;
-
     const lock_surface_mapped = blk: {
         if (server.lock_manager.lockSurfaceFromOutput(output)) |lock_surface| {
             break :blk lock_surface.wlr_lock_surface.surface.mapped;
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index 391e7fa..f3bee07 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -51,7 +51,6 @@ power_manager: *wlr.OutputPowerManagerV1,
 power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) = .init(handlePowerManagerSetMode),
 
 gamma_control_manager: *wlr.GammaControlManagerV1,
-gamma_control_set_gamma: wl.Listener(*wlr.GammaControlManagerV1.event.SetGamma) = .init(handleSetGamma),
 
 /// All Outputs that have a corresponding wlr_output.
 outputs: wl.list.Head(Output, .link),
@@ -60,15 +59,18 @@ pub fn init(om: *OutputManager) !void {
     const output_layout = try wlr.OutputLayout.create(server.wl_server);
     errdefer output_layout.destroy();
 
+    const gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server);
+    server.scene.wlr_scene.setGammaControlManagerV1(gamma_control_manager);
+
     om.* = .{
         .output_layout = output_layout,
         .outputs = undefined,
 
-        .presentation = try wlr.Presentation.create(server.wl_server, server.backend),
+        .presentation = try wlr.Presentation.create(server.wl_server, server.backend, 2),
         .xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
         .wlr_output_manager = try wlr.OutputManagerV1.create(server.wl_server),
         .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
-        .gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server),
+        .gamma_control_manager = gamma_control_manager,
     };
 
     om.outputs.init();
@@ -77,10 +79,13 @@ pub fn init(om: *OutputManager) !void {
     om.wlr_output_manager.events.apply.add(&om.manager_apply);
     om.wlr_output_manager.events.@"test".add(&om.manager_test);
     om.power_manager.events.set_mode.add(&om.power_manager_set_mode);
-    om.gamma_control_manager.events.set_gamma.add(&om.gamma_control_set_gamma);
 }
 
 pub fn deinit(om: *OutputManager) void {
+    om.manager_apply.link.remove();
+    om.manager_test.link.remove();
+    om.power_manager_set_mode.link.remove();
+
     om.output_layout.destroy();
 }
 
@@ -133,7 +138,7 @@ fn handleManagerApply(_: *wl.Listener(*wlr.OutputConfigurationV1), config: *wlr.
 
     var it = config.heads.iterator(.forward);
     while (it.next()) |head| {
-        const output: *Output = @ptrFromInt(head.state.output.data);
+        const output: *Output = @alignCast(@ptrCast(head.state.output.data));
 
         output.scheduled = .{
             .state = if (head.state.enabled) .enabled else .disabled_hard,
@@ -188,7 +193,7 @@ fn handlePowerManagerSetMode(
     event: *wlr.OutputPowerManagerV1.event.SetMode,
 ) void {
     // The output may have been destroyed, in which case there is nothing to do
-    const output = @as(?*Output, @ptrFromInt(event.output.data)) orelse return;
+    const output = @as(?*Output, @alignCast(@ptrCast(event.output.data))) orelse return;
 
     log.debug("client requested dpms {s} for output {s}", .{
         @tagName(event.mode),
@@ -208,19 +213,6 @@ fn handlePowerManagerSetMode(
     server.wm.dirtyWindowing();
 }
 
-fn handleSetGamma(
-    _: *wl.Listener(*wlr.GammaControlManagerV1.event.SetGamma),
-    event: *wlr.GammaControlManagerV1.event.SetGamma,
-) void {
-    // The output may have been destroyed, in which case there is nothing to do
-    const output = @as(?*Output, @ptrFromInt(event.output.data)) orelse return;
-
-    log.debug("client requested to set gamma", .{});
-
-    output.gamma_dirty = true;
-    event.output.scheduleFrame();
-}
-
 pub fn autoLayout(om: *OutputManager) void {
     // Find the right most edge of any non-autolayout output.
     var rightmost_edge: i32 = 0;
@@ -351,7 +343,7 @@ pub fn commitOutputState(om: *OutputManager) void {
         }
 
         for (states.items) |*state| {
-            const output: *Output = @ptrFromInt(state.output.data);
+            const output: *Output = @alignCast(@ptrCast(state.output.data));
             if (!output.scene_output.?.buildState(&state.base, &.{
                 .swapchain = swapchain_manager.getSwapchain(state.output),
             })) {
diff --git a/river/PointerConstraint.zig b/river/PointerConstraint.zig
index b9890b4..95aa4f7 100644
--- a/river/PointerConstraint.zig
+++ b/river/PointerConstraint.zig
@@ -47,7 +47,7 @@ commit: wl.Listener(*wlr.Surface) = .init(handleCommit),
 node_destroy: wl.Listener(void) = .init(handleNodeDestroy),
 
 pub fn create(wlr_constraint: *wlr.PointerConstraintV1) error{OutOfMemory}!void {
-    const seat: *Seat = @ptrFromInt(wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(wlr_constraint.seat.data));
 
     const constraint = try util.gpa.create(PointerConstraint);
     errdefer util.gpa.destroy(constraint);
@@ -55,7 +55,7 @@ pub fn create(wlr_constraint: *wlr.PointerConstraintV1) error{OutOfMemory}!void
     constraint.* = .{
         .wlr_constraint = wlr_constraint,
     };
-    wlr_constraint.data = @intFromPtr(constraint);
+    wlr_constraint.data = constraint;
 
     wlr_constraint.events.destroy.add(&constraint.destroy);
     wlr_constraint.surface.events.commit.add(&constraint.commit);
@@ -70,7 +70,7 @@ pub fn create(wlr_constraint: *wlr.PointerConstraintV1) error{OutOfMemory}!void
 }
 
 pub fn maybeActivate(constraint: *PointerConstraint) void {
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     assert(seat.cursor.constraint == constraint);
 
@@ -102,7 +102,7 @@ pub fn maybeActivate(constraint: *PointerConstraint) void {
 
 /// Called when the cursor position or content in the scene graph changes
 pub fn updateState(constraint: *PointerConstraint) void {
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     constraint.maybeActivate();
 
@@ -154,7 +154,7 @@ pub fn confine(constraint: *PointerConstraint, dx: *f64, dy: *f64) void {
 }
 
 pub fn deactivate(constraint: *PointerConstraint) void {
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     assert(seat.cursor.constraint == constraint);
     assert(constraint.state == .active);
@@ -167,7 +167,7 @@ pub fn deactivate(constraint: *PointerConstraint) void {
 }
 
 fn warpToHintIfSet(constraint: *PointerConstraint) void {
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     if (constraint.wlr_constraint.current.cursor_hint.enabled) {
         var lx: i32 = undefined;
@@ -190,7 +190,7 @@ fn handleNodeDestroy(listener: *wl.Listener(void)) void {
 
 fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.PointerConstraintV1) void {
     const constraint: *PointerConstraint = @fieldParentPtr("destroy", listener);
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     if (constraint.state == .active) {
         // We can't simply call deactivate() here as it calls sendDeactivated(),
@@ -215,7 +215,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.Point
 // the surface changes.
 fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     const constraint: *PointerConstraint = @fieldParentPtr("commit", listener);
-    const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(constraint.wlr_constraint.seat.data));
 
     switch (constraint.state) {
         .active => |state| {
diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig
index 2cabad8..5af3c05 100644
--- a/river/SceneNodeData.zig
+++ b/river/SceneNodeData.zig
@@ -46,7 +46,7 @@ pub fn attach(node: *wlr.SceneNode, data: Data) error{OutOfMemory}!void {
         .node = node,
         .data = data,
     };
-    node.data = @intFromPtr(scene_node_data);
+    node.data = scene_node_data;
 
     node.events.destroy.add(&scene_node_data.destroy);
 }
@@ -54,7 +54,7 @@ pub fn attach(node: *wlr.SceneNode, data: Data) error{OutOfMemory}!void {
 pub fn fromNode(node: *wlr.SceneNode) ?*SceneNodeData {
     var n = node;
     while (true) {
-        if (@as(?*SceneNodeData, @ptrFromInt(n.data))) |scene_node_data| {
+        if (@as(?*SceneNodeData, @alignCast(@ptrCast(n.data)))) |scene_node_data| {
             return scene_node_data;
         }
         if (n.parent) |parent_tree| {
@@ -66,7 +66,7 @@ pub fn fromNode(node: *wlr.SceneNode) ?*SceneNodeData {
 }
 
 pub fn fromSurface(surface: *wlr.Surface) ?*SceneNodeData {
-    if (@as(?*wlr.SceneNode, @ptrFromInt(surface.getRootSurface().data))) |node| {
+    if (@as(?*wlr.SceneNode, @alignCast(@ptrCast(surface.getRootSurface().data)))) |node| {
         return fromNode(node);
     }
     return null;
@@ -76,7 +76,7 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
     const scene_node_data: *SceneNodeData = @fieldParentPtr("destroy", listener);
 
     scene_node_data.destroy.link.remove();
-    scene_node_data.node.data = 0;
+    scene_node_data.node.data = null;
 
     util.gpa.destroy(scene_node_data);
 }
diff --git a/river/Seat.zig b/river/Seat.zig
index b24bde3..0f95fdf 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -190,7 +190,7 @@ pub fn create(name: [*:0]const u8) !void {
         .cursor = undefined,
         .relay = undefined,
     };
-    seat.wlr_seat.data = @intFromPtr(seat);
+    seat.wlr_seat.data = seat;
 
     server.input_manager.seats.append(seat);
     seat.link_sent.init();
@@ -561,7 +561,7 @@ pub fn focus(seat: *Seat, new_focus: Focus) void {
             if (seat.cursor.constraint) |constraint| {
                 assert(constraint.wlr_constraint == wlr_constraint);
             } else {
-                seat.cursor.constraint = @ptrFromInt(wlr_constraint.data);
+                seat.cursor.constraint = @alignCast(@ptrCast(wlr_constraint.data));
                 assert(seat.cursor.constraint != null);
             }
         }
@@ -581,7 +581,7 @@ pub fn keyboardEnterOrLeave(seat: *Seat, target_surface: ?*wlr.Surface) void {
 
 fn keyboardNotifyEnter(seat: *Seat, wlr_surface: *wlr.Surface) void {
     if (seat.wlr_seat.getKeyboard()) |wlr_keyboard| {
-        const keyboard: *Keyboard = @ptrFromInt(wlr_keyboard.data);
+        const keyboard: *Keyboard = @alignCast(@ptrCast(wlr_keyboard.data));
 
         var keycodes: std.BoundedArray(u32, Keyboard.Pressed.capacity) = .{};
         for (keyboard.pressed.keys.constSlice()) |item| {
diff --git a/river/Server.zig b/river/Server.zig
index f859453..10b028e 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -197,6 +197,9 @@ pub fn deinit(server: *Server) void {
     server.request_activate.link.remove();
     server.request_set_cursor_shape.link.remove();
 
+    server.input_manager.new_input.link.remove();
+    server.om.new_output.link.remove();
+
     if (build_options.xwayland) {
         if (server.xwayland) |xwayland| {
             server.new_xsurface.link.remove();
@@ -374,7 +377,7 @@ fn handleRequestSetCursorShape(
     _: *wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape),
     event: *wlr.CursorShapeManagerV1.event.RequestSetShape,
 ) void {
-    const seat: *Seat = @ptrFromInt(event.seat_client.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(event.seat_client.seat.data));
 
     if (event.tablet_tool) |wp_tool| {
         assert(event.device_type == .tablet_tool);
diff --git a/river/ShellSurface.zig b/river/ShellSurface.zig
index 0cf93a0..9b88df3 100644
--- a/river/ShellSurface.zig
+++ b/river/ShellSurface.zig
@@ -92,6 +92,8 @@ pub fn create(
 }
 
 fn handleDestroy(_: *river.ShellSurfaceV1, shell_surface: *ShellSurface) void {
+    shell_surface.surface.unmap();
+
     shell_surface.node.makeInert();
     shell_surface.node.deinit();
 
@@ -138,9 +140,7 @@ fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
 }
 
 fn commit(wlr_surface: *wlr.Surface) callconv(.C) void {
-    if (wlr_surface.unmap_commit) {
-        wlr_surface.unmap();
-    } else if (wlr_surface.hasBuffer()) {
+    if (wlr_surface.hasBuffer()) {
         wlr_surface.map();
     }
 }
diff --git a/river/TabletTool.zig b/river/TabletTool.zig
index dadac7b..c40312a 100644
--- a/river/TabletTool.zig
+++ b/river/TabletTool.zig
@@ -58,7 +58,7 @@ destroy: wl.Listener(*wlr.TabletTool) = .init(handleDestroy),
 set_cursor: wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor) = .init(handleSetCursor),
 
 pub fn get(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*TabletTool {
-    if (@as(?*TabletTool, @ptrFromInt(wlr_tool.data))) |tool| {
+    if (@as(?*TabletTool, @alignCast(@ptrCast(wlr_tool.data)))) |tool| {
         return tool;
     } else {
         return TabletTool.create(wlr_seat, wlr_tool);
@@ -80,7 +80,7 @@ fn create(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*Ta
         .wlr_cursor = wlr_cursor,
     };
 
-    wlr_tool.data = @intFromPtr(tool);
+    wlr_tool.data = tool;
 
     wlr_tool.events.destroy.add(&tool.destroy);
     tool.wp_tool.events.set_cursor.add(&tool.set_cursor);
@@ -91,7 +91,7 @@ fn create(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*Ta
 fn handleDestroy(listener: *wl.Listener(*wlr.TabletTool), _: *wlr.TabletTool) void {
     const tool: *TabletTool = @fieldParentPtr("destroy", listener);
 
-    tool.wp_tool.wlr_tool.data = 0;
+    tool.wp_tool.wlr_tool.data = null;
 
     tool.wlr_cursor.destroy();
 
diff --git a/river/TextInput.zig b/river/TextInput.zig
index f831d78..fcc1c73 100644
--- a/river/TextInput.zig
+++ b/river/TextInput.zig
@@ -39,7 +39,7 @@ disable: wl.Listener(*wlr.TextInputV3) = .init(handleDisable),
 destroy: wl.Listener(*wlr.TextInputV3) = .init(handleDestroy),
 
 pub fn create(wlr_text_input: *wlr.TextInputV3) !void {
-    const seat: *Seat = @ptrFromInt(wlr_text_input.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(wlr_text_input.seat.data));
 
     const text_input = try util.gpa.create(TextInput);
 
@@ -60,7 +60,7 @@ pub fn create(wlr_text_input: *wlr.TextInputV3) !void {
 
 fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
     const text_input: *TextInput = @fieldParentPtr("enable", listener);
-    const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(text_input.wlr_text_input.seat.data));
 
     if (text_input.wlr_text_input.focused_surface == null) {
         log.err("client requested to enable text input without focus, ignoring request", .{});
@@ -87,7 +87,7 @@ fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v
 
 fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
     const text_input: *TextInput = @fieldParentPtr("commit", listener);
-    const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(text_input.wlr_text_input.seat.data));
 
     if (seat.relay.text_input != text_input) {
         log.err("inactive text input tried to commit an update, client bug?", .{});
@@ -101,7 +101,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v
 
 fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
     const text_input: *TextInput = @fieldParentPtr("disable", listener);
-    const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(text_input.wlr_text_input.seat.data));
 
     if (seat.relay.text_input == text_input) {
         seat.relay.disableTextInput();
@@ -110,7 +110,7 @@ fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3)
 
 fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
     const text_input: *TextInput = @fieldParentPtr("destroy", listener);
-    const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(text_input.wlr_text_input.seat.data));
 
     if (seat.relay.text_input == text_input) {
         seat.relay.disableTextInput();
diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig
index 71d6230..9b3fd40 100644
--- a/river/XdgDecoration.zig
+++ b/river/XdgDecoration.zig
@@ -32,7 +32,7 @@ destroy: wl.Listener(*wlr.XdgToplevelDecorationV1) = wl.Listener(*wlr.XdgTopleve
 request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) = wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleRequestMode),
 
 pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
-    const toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.toplevel.base.data);
+    const toplevel: *XdgToplevel = @alignCast(@ptrCast(wlr_decoration.toplevel.base.data));
 
     toplevel.decoration = .{ .wlr_decoration = wlr_decoration };
     const decoration = &toplevel.decoration.?;
@@ -46,7 +46,7 @@ pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
 }
 
 pub fn deinit(decoration: *XdgDecoration) void {
-    const toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
+    const toplevel: *XdgToplevel = @alignCast(@ptrCast(decoration.wlr_decoration.toplevel.base.data));
 
     decoration.destroy.link.remove();
     decoration.request_mode.link.remove();
@@ -70,7 +70,7 @@ fn handleRequestMode(
 ) void {
     const decoration: *XdgDecoration = @fieldParentPtr("request_mode", listener);
 
-    const toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
+    const toplevel: *XdgToplevel = @alignCast(@ptrCast(decoration.wlr_decoration.toplevel.base.data));
     const window = toplevel.window;
 
     window.setDecorationHint(switch (decoration.wlr_decoration.requested_mode) {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 956f33e..e9ee408 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -97,8 +97,8 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
 
     toplevel.window = window;
 
-    wlr_toplevel.base.data = @intFromPtr(toplevel);
-    wlr_toplevel.base.surface.data = @intFromPtr(&window.tree.node);
+    wlr_toplevel.base.data = toplevel;
+    wlr_toplevel.base.surface.data = &window.tree.node;
 
     wlr_toplevel.events.destroy.add(&toplevel.destroy);
     wlr_toplevel.base.events.ack_configure.add(&toplevel.ack_configure);
@@ -235,7 +235,7 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
     toplevel.set_app_id.link.remove();
 
     // The wlr_surface may outlive the wlr_xdg_toplevel so we must clean up the user data.
-    toplevel.wlr_toplevel.base.surface.data = 0;
+    toplevel.wlr_toplevel.base.surface.data = null;
 
     const window = toplevel.window;
     window.impl = .destroying;
@@ -318,7 +318,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     switch (toplevel.configure_state) {
         .idle, .committed, .timed_out => {
             const old_geometry = toplevel.geometry;
-            toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
+            toplevel.geometry = toplevel.wlr_toplevel.base.geometry;
 
             const size_changed = toplevel.geometry.width != old_geometry.width or
                 toplevel.geometry.height != old_geometry.height;
@@ -343,7 +343,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
         // stashed buffer from when the transaction started.
         .inflight => window.sendFrameDone(),
         .acked, .timed_out_acked => {
-            toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
+            toplevel.geometry = toplevel.wlr_toplevel.base.geometry;
 
             window.rendering_scheduled.width = @intCast(toplevel.geometry.width);
             window.rendering_scheduled.height = @intCast(toplevel.geometry.height);
@@ -374,7 +374,7 @@ fn handleRequestMove(
 ) void {
     const toplevel: *XdgToplevel = @fieldParentPtr("request_move", listener);
     _ = toplevel;
-    const seat: *Seat = @ptrFromInt(event.seat.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(event.seat.seat.data));
 
     // Moving windows with touch or tablet tool is not yet supported.
     if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
@@ -385,7 +385,7 @@ fn handleRequestMove(
 fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
     const toplevel: *XdgToplevel = @fieldParentPtr("request_resize", listener);
     _ = toplevel;
-    const seat: *Seat = @ptrFromInt(event.seat.seat.data);
+    const seat: *Seat = @alignCast(@ptrCast(event.seat.seat.data));
 
     // Resizing windows with touch or tablet tool is not yet supported.
     if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index e5554dc..95fbe39 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -124,7 +124,7 @@ fn mapImpl(override_redirect: *XwaylandOverrideRedirect) error{OutOfMemory}!void
         .override_redirect = override_redirect,
     });
 
-    surface.data = @intFromPtr(&override_redirect.surface_tree.?.node);
+    surface.data = &override_redirect.surface_tree.?.node;
 
     override_redirect.surface_tree.?.node.setPosition(
         override_redirect.xsurface.x,
@@ -163,7 +163,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
 
     override_redirect.set_geometry.link.remove();
 
-    override_redirect.xsurface.surface.?.data = 0;
+    override_redirect.xsurface.surface.?.data = null;
     override_redirect.surface_tree.?.node.destroy();
     override_redirect.surface_tree = null;
 
diff --git a/river/XwaylandWindow.zig b/river/XwaylandWindow.zig
index cc3cd65..d60d0df 100644
--- a/river/XwaylandWindow.zig
+++ b/river/XwaylandWindow.zig
@@ -183,7 +183,7 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
 
     const xsurface = xwindow.xsurface;
     const surface = xsurface.surface.?;
-    surface.data = @intFromPtr(&window.tree.node);
+    surface.data = &window.tree.node;
 
     xwindow.surface_tree = window.surface_tree.createSceneSubsurfaceTree(surface) catch {
         log.err("out of memory", .{});
@@ -210,7 +210,7 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
 fn handleUnmap(listener: *wl.Listener(void)) void {
     const xwindow: *XwaylandWindow = @fieldParentPtr("unmap", listener);
 
-    xwindow.xsurface.surface.?.data = 0;
+    xwindow.xsurface.surface.?.data = null;
 
     xwindow.window.unmap();