From 1cfafc4fb37d9f9ced6fde85a3f2b23656625af7 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Sat, 14 Feb 2026 15:34:46 -0600 Subject: [PATCH] Implement the rest of river_layer_shell_v1 I still needed to call setDefault, as well as handle the .focus_* events from the river_layer_shell_seat_v1. I also fixed a memory leak where the output wasn't destroying its river_layer_shell_output_v1. --- src/Output.zig | 2 +- src/Seat.zig | 105 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/Output.zig b/src/Output.zig index e1e34a5..02bd96d 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -118,10 +118,10 @@ pub fn destroy(output: *Output) void { window.link.remove(); window.destroy(); } - output.tag_layout_overrides.deinit(utils.gpa); output.deinitWallpaperLayerSurface(); output.river_output_v1.destroy(); + output.river_layer_shell_output_v1.destroy(); utils.gpa.destroy(output); } diff --git a/src/Seat.zig b/src/Seat.zig index ba7c4dd..0bcda3e 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -7,9 +7,11 @@ const Seat = @This(); context: *Context, river_seat_v1: *river.SeatV1, +river_layer_shell_seat_v1: *river.LayerShellSeatV1, focused_window: ?*Window, focused_output: ?*Output, +layer_focus: LayerFocus = .focus_none, pointer_op: PointerOp = .none, @@ -22,9 +24,14 @@ link: wl.list.Link, move_pointer_binding: ?*river.PointerBindingV1 = null, resize_pointer_binding: ?*river.PointerBindingV1 = null, +// We just steal the Event's tag type to use as our enum. If another event is added +// that's *not* for focus, we'll have to create our own enum and just keep it in sync. +pub const LayerFocus = @typeInfo(river.LayerShellSeatV1.Event).@"union".tag_type.?; + pub const PendingManage = struct { window: ?PendingWindow = null, output: ?PendingOutput = null, + layer_focus: ?LayerFocus = null, should_warp_pointer: bool = false, op_delta: ?struct { dx: i32, dy: i32 } = null, @@ -63,12 +70,14 @@ pub fn create(context: *Context, river_seat_v1: *river.SeatV1) !*Seat { seat.* = .{ .context = context, .river_seat_v1 = river_seat_v1, + .river_layer_shell_seat_v1 = try context.river_layer_shell_v1.getSeat(river_seat_v1), .focused_window = null, .focused_output = null, .link = undefined, // Handled by the wl.list }; seat.river_seat_v1.setListener(*Seat, seatListener, seat); + seat.river_layer_shell_seat_v1.setListener(*Seat, riverLayerShellSeatListener, seat); return seat; } @@ -77,6 +86,7 @@ pub fn destroy(seat: *Seat) void { if (seat.move_pointer_binding) |binding| binding.destroy(); if (seat.resize_pointer_binding) |binding| binding.destroy(); seat.river_seat_v1.destroy(); + seat.river_layer_shell_seat_v1.destroy(); utils.gpa.destroy(seat); } @@ -103,6 +113,11 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * } } +fn riverLayerShellSeatListener(river_layer_shell_seat_v1: *river.LayerShellSeatV1, event: river.LayerShellSeatV1.Event, seat: *Seat) void { + assert(seat.river_layer_shell_seat_v1 == river_layer_shell_seat_v1); + seat.pending_manage.layer_focus = std.meta.activeTag(event); +} + // river_window_v1 needs to be optional because ev.window is optional fn setWindowFocus(seat: *Seat, river_window_v1: ?*river.WindowV1) void { const wv1 = river_window_v1 orelse return; @@ -116,33 +131,37 @@ fn setWindowFocus(seat: *Seat, river_window_v1: ?*river.WindowV1) void { pub fn manage(seat: *Seat) void { defer seat.pending_manage = .{}; - if (seat.pending_manage.window) |pending_window| { - switch (pending_window) { - .window => |window| { - if (seat.focused_window) |focused| { - // Tell the previously focused Window that it's no longer focused - if (focused != window) { + // Focus events are ignored by the compositor when a layer shell has exclusive focus. + if (seat.layer_focus != .focus_exclusive) { + if (seat.pending_manage.window) |pending_window| { + switch (pending_window) { + .window => |window| { + if (seat.focused_window) |focused| { + // Tell the previously focused Window that it's no longer focused + if (focused != window) { + focused.pending_render.focused = false; + } + } + seat.focused_window = window; + seat.river_seat_v1.focusWindow(window.river_window_v1); + window.pending_render.focused = true; + }, + .clear_focus => { + if (seat.focused_window) |focused| { + // Tell the previously focused Window that it's no longer focused focused.pending_render.focused = false; } - } - seat.focused_window = window; - seat.river_seat_v1.focusWindow(window.river_window_v1); - window.pending_render.focused = true; - }, - .clear_focus => { - if (seat.focused_window) |focused| { - // Tell the previously focused Window that it's no longer focused - focused.pending_render.focused = false; - } - seat.focused_window = null; - seat.river_seat_v1.clearFocus(); - }, + seat.focused_window = null; + seat.river_seat_v1.clearFocus(); + }, + } } } if (seat.pending_manage.output) |pending_output| { switch (pending_output) { .output => |output| { seat.focused_output = output; + output.river_layer_shell_output_v1.setDefault(); }, .clear_focus => { seat.focused_output = null; @@ -150,18 +169,42 @@ pub fn manage(seat: *Seat) void { } } - if (seat.pending_manage.should_warp_pointer) blk: { - if (seat.context.config.pointer_warp_on_focus_change) { - const window = seat.focused_window orelse { - log.warn("Trying to warp-on-focus-change without a focused window.", .{}); - break :blk; - }; - // Warp pointer to center of focused window; - // because the x and y coords are set during render, we need to check if - // there are new coordinates in window.pending_render. - const pointer_x: i32 = (window.pending_render.x orelse window.x) + @divFloor(window.width, 2); - const pointer_y: i32 = (window.pending_render.y orelse window.y) + @divFloor(window.height, 2); - seat.river_seat_v1.pointerWarp(pointer_x, pointer_y); + // Handle layer focus changes after window focus so focused_window is up to date. + // This overrides whatever pending_render.focused the window focus block just set. + if (seat.pending_manage.layer_focus) |layer_focus| { + seat.layer_focus = layer_focus; + switch (layer_focus) { + .focus_exclusive, + .focus_non_exclusive, + => { + if (seat.focused_window) |focused_window| { + focused_window.pending_render.focused = false; + } + }, + .focus_none => { + if (seat.focused_window) |focused_window| { + seat.river_seat_v1.focusWindow(focused_window.river_window_v1); + focused_window.pending_render.focused = true; + } + }, + } + } + + // Focus doesn't change during .focus_exclusive, so pointer shouldn't get warped, either + if (seat.layer_focus != .focus_exclusive) { + if (seat.pending_manage.should_warp_pointer) blk: { + if (seat.context.config.pointer_warp_on_focus_change) { + const window = seat.focused_window orelse { + log.warn("Trying to warp-on-focus-change without a focused window.", .{}); + break :blk; + }; + // Warp pointer to center of focused window; + // because the x and y coords are set during render, we need to check if + // there are new coordinates in window.pending_render. + const pointer_x: i32 = (window.pending_render.x orelse window.x) + @divFloor(window.width, 2); + const pointer_y: i32 = (window.pending_render.y orelse window.y) + @divFloor(window.height, 2); + seat.river_seat_v1.pointerWarp(pointer_x, pointer_y); + } } }