diff --git a/src/Seat.zig b/src/Seat.zig index b135ccb..749a01d 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -10,14 +10,12 @@ 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 = .{}, +/// State consumed in manage phase, reset at end of manage(). +pending_manage: PendingManage = .{}, link: wl.list.Link, -/// Used to manage updates to various aspect of Seat's state -pub const PendingState = struct { +pub const PendingManage = struct { pending_focus: ?PendingFocus = null, should_warp_pointer: bool = false, @@ -51,12 +49,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.pending_state.pending_focus = .{ .window = window }; + seat.pending_manage.pending_focus = .{ .window = window }; }, .window_interaction => |ev| { const window_v1 = ev.window orelse return; const window: *Window = @ptrCast(@alignCast(window_v1.getUserData())); - seat.pending_state.pending_focus = .{ .window = window }; + seat.pending_manage.pending_focus = .{ .window = window }; }, else => |ev| { log.debug("unhandled event: {s}", .{@tagName(ev)}); @@ -65,32 +63,42 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * } pub fn manage(seat: *Seat) void { - defer seat.pending_state = .{}; + defer seat.pending_manage = .{}; - log.debug("Managing Seat, pending state={any}", .{seat.pending_state}); - - if (seat.pending_state.pending_focus) |pending_focus| { + if (seat.pending_manage.pending_focus) |pending_focus| { switch (pending_focus) { .window => |window| { + if (seat.focused) |focused| { + // Tell the previously focused Window that it's no longer focused + if (focused != window) { + focused.pending_render.focused = false; + } + } seat.focused = window; seat.seat_v1.focusWindow(window.window_v1); + window.pending_render.focused = true; }, .clear_focus => { + if (seat.focused) |focused| { + // Tell the previously focused Window that it's no longer focused + focused.pending_render.focused = false; + } seat.focused = null; seat.seat_v1.clearFocus(); }, } } - if (seat.pending_state.should_warp_pointer) blk: { + if (seat.pending_manage.should_warp_pointer) { const window = seat.focused orelse { log.err("Trying to warp pointer without focused window.", .{}); - break :blk; + return; }; // 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); + // Warp pointer to center of focused window; + // because the x and y coords are set later, we need to check them here. + const pointer_x: i32 = (window.pending_render.x orelse window.x) + @divTrunc(window.width, 2); + const pointer_y: i32 = (window.pending_render.y orelse window.y) + @divTrunc(window.height, 2); seat.seat_v1.pointerWarp(pointer_x, pointer_y); log.debug("warped pointer to {}, {}", .{ pointer_x, pointer_y }); } diff --git a/src/Window.zig b/src/Window.zig index bd58c28..4ea099e 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -19,26 +19,29 @@ maximized: bool = false, initialized: bool = false, -/// Used to manage updates to various aspect of Window's state -/// Gets reset at the end of every Window.manage() call. -/// -/// It might be a good idea to have two separate versions of this so one -/// can be used in renders, but I think it's okay for now. -pending_state: PendingState = .{}, +/// State consumed in manage() phase, reset at end of manage(). +pending_manage: PendingManage = .{}, + +/// State consumed in render() phase, reset at end of render(). +pending_render: PendingRender = .{}, link: wl.list.Link, -/// Used to manage updates to various aspect of Window's state -pub const PendingState = struct { +pub const PendingManage = struct { width: ?u31 = null, height: ?u31 = null, - x: ?i32 = null, - y: ?i32 = null, fullscreen: ?bool = null, maximized: ?bool = null, }; +pub const PendingRender = struct { + x: ?i32 = null, + y: ?i32 = null, + + focused: ?bool = null, +}; + pub fn init(window: *Window, context: *Context, river_window_v1: *river.WindowV1) void { window.* = .{ .context = context, @@ -61,7 +64,18 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, var it = window.context.wm.seats.iterator(.forward); while (it.next()) |seat| { if (seat.focused == window) { - seat.focused = null; + // Find another window to focus and warp pointer there + if (window.context.wm.getPrevWindow(window)) |next_focus| { + if (next_focus != window) { + seat.pending_manage.pending_focus = .{ .window = next_focus }; + seat.pending_manage.should_warp_pointer = true; + } else { + // Only window in list - clear focus + seat.pending_manage.pending_focus = .clear_focus; + } + } else { + seat.pending_manage.pending_focus = .clear_focus; + } } } } @@ -97,25 +111,19 @@ pub fn manage(window: *Window) void { } // Any new state since the last manage event - defer window.pending_state = .{}; - const pending_state = window.pending_state; + defer window.pending_manage = .{}; + const pending_manage = window.pending_manage; // Layout (pre-computed by WindowManager.calculatePrimaryStackLayout()) - if (pending_state.width) |new_width| { - if (pending_state.height) |new_height| { + if (pending_manage.width) |new_width| { + if (pending_manage.height) |new_height| { window.width = new_width; window.height = new_height; window.window_v1.proposeDimensions(new_width, new_height); } } - if (pending_state.x) |new_x| { - window.x = new_x; - } - if (pending_state.y) |new_y| { - window.y = new_y; - } // Fullscreen and maximize operations - if (pending_state.fullscreen) |fullscreen| { + if (pending_manage.fullscreen) |fullscreen| { window.fullscreen = fullscreen; if (fullscreen) { const output = window.context.wm.outputs.first() orelse @panic("failed to get output"); @@ -126,7 +134,7 @@ pub fn manage(window: *Window) void { window.window_v1.informNotFullscreen(); } } - if (pending_state.maximized) |maximized| { + if (pending_manage.maximized) |maximized| { window.maximized = maximized; if (maximized) { window.window_v1.informMaximized(); @@ -137,34 +145,38 @@ pub fn manage(window: *Window) void { } pub fn render(window: *Window) void { - // Set the window position using the pre-computed layout - window.node_v1.setPosition(window.x, window.y); + defer window.pending_render = .{}; + + // TODO: We probably could just move these back to pending_manage and have PendingRiver.new_coords: bool + // This would also simplify the pointer warp behaviour in Seat.manage() + if (window.pending_render.x) |new_x| { + if (window.pending_render.y) |new_y| { + window.x = new_x; + window.y = new_y; + + window.node_v1.setPosition(window.x, window.y); + } else { + log.err("Window.pending_render with only x set", .{}); + } + } else if (window.pending_render.y) |_| { + log.err("Window.pending_render with only y set", .{}); + } // Set borders if (!window.fullscreen) { - const border_width = window.context.config.border_width; - if (window.isFocused()) { - const border_color_focused = window.context.config.border_color_focused; - window.window_v1.setBorders(.{ .top = true, .bottom = true, .left = true, .right = true }, border_width, border_color_focused.red, border_color_focused.green, border_color_focused.blue, border_color_focused.alpha); - } else { - const border_color_unfocused = window.context.config.border_color_unfocused; - window.window_v1.setBorders(.{ .top = true, .bottom = true, .left = true, .right = true }, border_width, border_color_unfocused.red, border_color_unfocused.green, border_color_unfocused.blue, border_color_unfocused.alpha); + if (window.pending_render.focused) |focused| { + const border_width = window.context.config.border_width; + if (focused) { + const border_color_focused = window.context.config.border_color_focused; + window.window_v1.setBorders(.{ .top = true, .bottom = true, .left = true, .right = true }, border_width, border_color_focused.red, border_color_focused.green, border_color_focused.blue, border_color_focused.alpha); + } else { + const border_color_unfocused = window.context.config.border_color_unfocused; + window.window_v1.setBorders(.{ .top = true, .bottom = true, .left = true, .right = true }, border_width, border_color_unfocused.red, border_color_unfocused.green, border_color_unfocused.blue, border_color_unfocused.alpha); + } } } } -// TODO: Should probably make this better. Shouldn't be too bad since we only have one seat -// but there's no reason to do it like this -fn isFocused(window: *Window) bool { - var it = window.context.wm.seats.iterator(.forward); - while (it.next()) |seat| { - if (seat.focused == window) { - return true; - } - } - return false; -} - const std = @import("std"); const assert = std.debug.assert; diff --git a/src/WindowManager.zig b/src/WindowManager.zig index 7440dce..6d1f06f 100644 --- a/src/WindowManager.zig +++ b/src/WindowManager.zig @@ -87,11 +87,11 @@ fn calculatePrimaryStackLayout(wm: *WindowManager) void { while (it.next()) |window| { if (count == 1) { // Single window: maximize - window.pending_state.x = output_x; - window.pending_state.y = output_y; - window.pending_state.width = output_width; - window.pending_state.height = output_height; - window.pending_state.maximized = true; + window.pending_render.x = output_x; + window.pending_render.y = output_y; + window.pending_manage.width = output_width; + window.pending_manage.height = output_height; + window.pending_manage.maximized = true; } else { // Multiple windows: primary/stack layout // TODO: Support multiple windows in primary stack @@ -99,25 +99,25 @@ fn calculatePrimaryStackLayout(wm: *WindowManager) void { const stack_width: u31 = output_width - primary_width; const stack_count = count - 1; const stack_height: u31 = @divFloor(output_height, stack_count); - window.pending_state.maximized = false; + window.pending_manage.maximized = false; if (index == 0) { // Primary window (first window) - right side - window.pending_state.x = output_x + @as(i32, stack_width); - window.pending_state.y = output_y; - window.pending_state.width = primary_width; - window.pending_state.height = output_height; + window.pending_render.x = output_x + @as(i32, stack_width); + window.pending_render.y = output_y; + window.pending_manage.width = primary_width; + window.pending_manage.height = output_height; } else { // Stack window(s) - left side const stack_index = index - 1; - window.pending_state.x = output_x; - window.pending_state.y = output_y + @as(i32, stack_index) * @as(i32, stack_height); - window.pending_state.width = stack_width; + window.pending_render.x = output_x; + window.pending_render.y = output_y + @as(i32, stack_index) * @as(i32, stack_height); + window.pending_manage.width = stack_width; // Last stack window gets remaining height to avoid gaps from integer division if (index == count - 1) { - window.pending_state.height = output_height - stack_index * stack_height; + window.pending_manage.height = output_height - stack_index * stack_height; } else { - window.pending_state.height = stack_height; + window.pending_manage.height = stack_height; } } } @@ -125,10 +125,10 @@ fn calculatePrimaryStackLayout(wm: *WindowManager) void { // Borders are automatically disabled when a window is fullscreened so we don't // have to worry about that. const border_width = wm.context.config.border_width; - window.pending_state.height.? -= 2 * border_width; - window.pending_state.width.? -= 2 * border_width; - window.pending_state.x.? += border_width; - window.pending_state.y.? += border_width; + window.pending_manage.height.? -= 2 * border_width; + window.pending_manage.width.? -= 2 * border_width; + window.pending_render.x.? += border_width; + window.pending_render.y.? += border_width; index += 1; } @@ -143,6 +143,7 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv std.posix.exit(1); }, .manage_start => { + log.debug("manage start", .{}); if (!context.initialized) { // This code (should) only be run once while initializing the WM, so let's // mark it as cold. @@ -159,12 +160,6 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.e, .{ .mod4 = true, .shift = true }, .exit); } - { - var it = wm.seats.iterator(.forward); - while (it.next()) |seat| { - seat.manage(); - } - } { var it = wm.outputs.iterator(.forward); while (it.next()) |output| { @@ -179,9 +174,17 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv window.manage(); } } + { + var it = wm.seats.iterator(.forward); + while (it.next()) |seat| { + seat.manage(); + } + } window_manager_v1.manageFinish(); + log.debug("manage end===================", .{}); }, .render_start => { + log.debug("render start", .{}); { var it = wm.seats.iterator(.forward); while (it.next()) |seat| { @@ -201,6 +204,7 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv } } window_manager_v1.renderFinish(); + log.debug("render end==================", .{}); }, .output => |ev| { // TODO: Support multi-output @@ -228,8 +232,8 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv // TODO - CONFIG: Allow appending window instead of prepending wm.windows.prepend(window); const seat = wm.seats.first() orelse @panic("Failed to get seat"); - seat.pending_state.pending_focus = .{ .window = window }; - seat.pending_state.should_warp_pointer = true; + seat.pending_manage.pending_focus = .{ .window = window }; + seat.pending_manage.should_warp_pointer = true; wm.window_count += 1; }, diff --git a/src/XkbBindings.zig b/src/XkbBindings.zig index 4be1408..2f9ea5f 100644 --- a/src/XkbBindings.zig +++ b/src/XkbBindings.zig @@ -49,7 +49,7 @@ 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 \"{s}\": {}", .{ cmd, err }); + log.err("Failed to spawn \"{s}\": {}", .{ cmd[0], err }); }; }, .focus_next => { @@ -61,10 +61,10 @@ const XkbBinding = struct { context.wm.windows.first(); if (pending_focus) |window| { - seat.pending_state.pending_focus = .{ .window = window }; - seat.pending_state.should_warp_pointer = true; + seat.pending_manage.pending_focus = .{ .window = window }; + seat.pending_manage.should_warp_pointer = true; } else { - seat.pending_state.pending_focus = .clear_focus; + seat.pending_manage.pending_focus = .clear_focus; } // context.wm.window_manager_v1.manageDirty(); }, @@ -77,32 +77,23 @@ const XkbBinding = struct { context.wm.windows.last(); if (pending_focus) |window| { - seat.pending_state.pending_focus = .{ .window = window }; - seat.pending_state.should_warp_pointer = true; + seat.pending_manage.pending_focus = .{ .window = window }; + seat.pending_manage.should_warp_pointer = true; } else { - seat.pending_state.pending_focus = .clear_focus; + seat.pending_manage.pending_focus = .clear_focus; } // 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; + window.pending_manage.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 => {