diff --git a/src/Seat.zig b/src/Seat.zig index 15e90f7..b135ccb 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -10,8 +10,23 @@ seat_v1: *river.SeatV1, focused: ?*Window, +/// Used to manage updates to various aspect of Seat's state +/// Gets reset at the end of every Seat.manage() call. +pending_state: PendingState = .{}, + link: wl.list.Link, +/// Used to manage updates to various aspect of Seat's state +pub const PendingState = struct { + pending_focus: ?PendingFocus = null, + should_warp_pointer: bool = false, + + pub const PendingFocus = union(enum) { + window: *Window, + clear_focus, + }; +}; + pub fn init(seat: *Seat, context: *Context, river_seat_v1: *river.SeatV1) void { seat.* = .{ .context = context, @@ -36,12 +51,12 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * .pointer_enter => |ev| { const window_v1 = ev.window orelse return; const window: *Window = @ptrCast(@alignCast(window_v1.getUserData())); - seat.focused = window; + seat.pending_state.pending_focus = .{ .window = window }; }, .window_interaction => |ev| { const window_v1 = ev.window orelse return; const window: *Window = @ptrCast(@alignCast(window_v1.getUserData())); - seat.focused = window; + seat.pending_state.pending_focus = .{ .window = window }; }, else => |ev| { log.debug("unhandled event: {s}", .{@tagName(ev)}); @@ -50,8 +65,34 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * } pub fn manage(seat: *Seat) void { - if (seat.focused) |window| { - seat.seat_v1.focusWindow(window.window_v1); + defer seat.pending_state = .{}; + + log.debug("Managing Seat, pending state={any}", .{seat.pending_state}); + + if (seat.pending_state.pending_focus) |pending_focus| { + switch (pending_focus) { + .window => |window| { + seat.focused = window; + seat.seat_v1.focusWindow(window.window_v1); + }, + .clear_focus => { + seat.focused = null; + seat.seat_v1.clearFocus(); + }, + } + } + + if (seat.pending_state.should_warp_pointer) blk: { + const window = seat.focused orelse { + log.err("Trying to warp pointer without focused window.", .{}); + break :blk; + }; + // TODO - CONFIG: Allow disabling this behaviour + // Warp pointer to center of newly-focused windows + const pointer_x: i32 = @divTrunc(window.x + window.width, 2); + const pointer_y: i32 = @divTrunc(window.y + window.height, 2); + seat.seat_v1.pointerWarp(pointer_x, pointer_y); + log.debug("warped pointer to {}, {}", .{ pointer_x, pointer_y }); } } diff --git a/src/WindowManager.zig b/src/WindowManager.zig index dc80c00..7440dce 100644 --- a/src/WindowManager.zig +++ b/src/WindowManager.zig @@ -69,7 +69,7 @@ pub fn getPrevWindow(wm: *WindowManager, current: *Window) ?*Window { } /// Calculate primary/stack layout positions for all windows. -/// - Single window: fullscreen +/// - Single window: maximized /// - Multiple windows: stack (45% left, vertically tiled), primary (55% right) fn calculatePrimaryStackLayout(wm: *WindowManager) void { const count = wm.window_count; @@ -149,7 +149,7 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv @branchHint(.cold); context.initialized = true; - const seat = wm.seats.first() orelse @panic("No river_seat_v1 found"); + const seat = wm.seats.first() orelse @panic("Failed to get seat"); const river_seat_v1 = seat.seat_v1; context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.t, .{ .mod4 = true }, .{ .spawn = &.{"foot"} }); context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.j, .{ .mod4 = true }, .focus_next); @@ -225,13 +225,13 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv var window = utils.allocator.create(Window) catch @panic("out-of-memory; exiting."); window.init(context, ev.id); - // TODO: Allow appending window instead of prepending + // TODO - CONFIG: Allow appending window instead of prepending wm.windows.prepend(window); const seat = wm.seats.first() orelse @panic("Failed to get seat"); - seat.focused = window; + seat.pending_state.pending_focus = .{ .window = window }; + seat.pending_state.should_warp_pointer = true; wm.window_count += 1; - log.debug("window_count = {d}", .{wm.window_count}); }, else => |ev| { log.debug("unhandled event: {s}", .{@tagName(ev)}); diff --git a/src/XkbBindings.zig b/src/XkbBindings.zig index e6e29ff..4be1408 100644 --- a/src/XkbBindings.zig +++ b/src/XkbBindings.zig @@ -49,38 +49,60 @@ const XkbBinding = struct { .spawn => |cmd| { var child = std.process.Child.init(cmd, std.heap.c_allocator); _ = child.spawn() catch |err| { - log.err("Failed to spawn foot: {}", .{err}); + log.err("Failed to spawn \"{s}\": {}", .{ cmd, err }); }; }, .focus_next => { const seat = context.wm.seats.first() orelse return; - if (seat.focused) |current| { - seat.focused = context.wm.getNextWindow(current); - } else { + const pending_focus = if (seat.focused) |current| + context.wm.getNextWindow(current) + else // No window focused, focus the first one - seat.focused = context.wm.windows.first(); + context.wm.windows.first(); + + if (pending_focus) |window| { + seat.pending_state.pending_focus = .{ .window = window }; + seat.pending_state.should_warp_pointer = true; + } else { + seat.pending_state.pending_focus = .clear_focus; } - context.wm.window_manager_v1.manageDirty(); + // context.wm.window_manager_v1.manageDirty(); }, .focus_prev => { const seat = context.wm.seats.first() orelse return; - if (seat.focused) |current| { - seat.focused = context.wm.getPrevWindow(current); - } else { + const pending_focus = if (seat.focused) |current| + context.wm.getPrevWindow(current) + else // No window focused, focus the last one - seat.focused = context.wm.windows.last(); + context.wm.windows.last(); + + if (pending_focus) |window| { + seat.pending_state.pending_focus = .{ .window = window }; + seat.pending_state.should_warp_pointer = true; + } else { + seat.pending_state.pending_focus = .clear_focus; } - context.wm.window_manager_v1.manageDirty(); + // context.wm.window_manager_v1.manageDirty(); }, .fullscreen => { const seat = context.wm.seats.first() orelse return; const window = seat.focused orelse return; window.pending_state.fullscreen = !window.fullscreen; + // context.wm.window_manager_v1.manageDirty(); }, .close_window => { const seat = context.wm.seats.first() orelse return; if (seat.focused) |window| { window.window_v1.close(); + + // Move focus to previous window in stack (if one exists) + if (context.wm.getPrevWindow(window)) |pending_focus| { + seat.pending_state.pending_focus = .{ .window = pending_focus }; + seat.pending_state.should_warp_pointer = true; + } else { + seat.pending_state.pending_focus = .clear_focus; + } + // context.wm.window_manager_v1.manageDirty(); } }, .exit => {