Merge pull request 'Clean up some code, implement pointer-warp-on-focus-change, fix a crash' (#3) from pointer-warp-on-focus-change into main
Reviewed-on: https://codeberg.org/bwbuhse/beansprout/pulls/3
This commit is contained in:
commit
67eef42bd8
6 changed files with 248 additions and 125 deletions
71
src/Seat.zig
71
src/Seat.zig
|
|
@ -10,8 +10,21 @@ seat_v1: *river.SeatV1,
|
|||
|
||||
focused: ?*Window,
|
||||
|
||||
/// State consumed in manage phase, reset at end of manage().
|
||||
pending_manage: PendingManage = .{},
|
||||
|
||||
link: wl.list.Link,
|
||||
|
||||
pub const PendingManage = 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,
|
||||
|
|
@ -33,25 +46,59 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *
|
|||
.wl_seat => {
|
||||
// log.debug("initializing new river_seat_v1 corresponding to wl_seat: {d}", .{ev.name});
|
||||
},
|
||||
.pointer_enter => |ev| {
|
||||
const window_v1 = ev.window orelse return;
|
||||
const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
|
||||
seat.focused = window;
|
||||
},
|
||||
.window_interaction => |ev| {
|
||||
const window_v1 = ev.window orelse return;
|
||||
const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
|
||||
seat.focused = window;
|
||||
},
|
||||
.pointer_enter => |ev| seat.setWindowFocus(ev.window),
|
||||
.window_interaction => |ev| seat.setWindowFocus(ev.window),
|
||||
else => |ev| {
|
||||
log.debug("unhandled event: {s}", .{@tagName(ev)});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn setWindowFocus(seat: *Seat, window_v1: ?*river.WindowV1) void {
|
||||
const wv1 = window_v1 orelse return;
|
||||
const window: *Window = @ptrCast(@alignCast(wv1.getUserData()));
|
||||
seat.pending_manage.pending_focus = .{ .window = window };
|
||||
}
|
||||
|
||||
pub fn manage(seat: *Seat) void {
|
||||
if (seat.focused) |window| {
|
||||
seat.seat_v1.focusWindow(window.window_v1);
|
||||
defer seat.pending_manage = .{};
|
||||
|
||||
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_manage.should_warp_pointer) {
|
||||
const window = seat.focused orelse {
|
||||
log.err("Trying to warp pointer without focused window.", .{});
|
||||
return;
|
||||
};
|
||||
// TODO - CONFIG: Allow disabling this behaviour
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
107
src/Window.zig
107
src/Window.zig
|
|
@ -19,31 +19,34 @@ 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,
|
||||
.window_v1 = river_window_v1,
|
||||
.node_v1 = river_window_v1.getNode() catch @panic("failed to get node"),
|
||||
.node_v1 = river_window_v1.getNode() catch @panic("Failed to get node"),
|
||||
.link = undefined, // Handled by the wl.list
|
||||
};
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,12 +98,12 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event,
|
|||
}
|
||||
|
||||
pub fn manage(window: *Window) void {
|
||||
// Use server-side decoration (no client-drawn title bars)
|
||||
// TODO: Probably shouldn't send this for every manage(?)
|
||||
if (!window.initialized) {
|
||||
@branchHint(.unlikely);
|
||||
window.initialized = true;
|
||||
|
||||
// TODO: We might want to think about paying attention to the decoration_hint event
|
||||
// If we do, this would need to move, I think?
|
||||
window.window_v1.useSsd();
|
||||
|
||||
window.window_v1.setCapabilities(.{ .fullscreen = true, .maximize = true });
|
||||
|
|
@ -97,28 +111,22 @@ 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");
|
||||
const output = window.context.wm.outputs.first() orelse @panic("Failed to get output");
|
||||
window.window_v1.fullscreen(output.output_v1);
|
||||
window.window_v1.informFullscreen();
|
||||
} else {
|
||||
|
|
@ -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,32 +145,39 @@ 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| {
|
||||
if (focused) {
|
||||
window.applyBorders(window.context.config.border_color_focused);
|
||||
} else {
|
||||
window.applyBorders(window.context.config.border_color_unfocused);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
fn applyBorders(window: *Window, color: utils.RiverColor) void {
|
||||
const border_width = window.context.config.border_width;
|
||||
const all_sides = river.WindowV1.Edges{ .top = true, .bottom = true, .left = true, .right = true };
|
||||
window.window_v1.setBorders(all_sides, border_width, color.red, color.green, color.blue, color.alpha);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ const WindowManager = @This();
|
|||
|
||||
const PRIMARY_RATIO: f32 = 0.55;
|
||||
|
||||
const MIN_RIVER_SEAT_V1_VERSION: u2 = 3;
|
||||
|
||||
context: *Context,
|
||||
|
||||
window_manager_v1: *river.WindowManagerV1,
|
||||
|
|
@ -38,6 +40,28 @@ pub fn create(context: *Context, window_manager_v1: *river.WindowManagerV1) !*Wi
|
|||
}
|
||||
|
||||
pub fn destroy(wm: *WindowManager) void {
|
||||
var window_it = wm.windows.iterator(.forward);
|
||||
while (window_it.next()) |window| {
|
||||
window.window_v1.destroy();
|
||||
window.node_v1.destroy();
|
||||
window.link.remove();
|
||||
utils.allocator.destroy(window);
|
||||
}
|
||||
|
||||
var output_it = wm.outputs.iterator(.forward);
|
||||
while (output_it.next()) |output| {
|
||||
output.output_v1.destroy();
|
||||
output.link.remove();
|
||||
utils.allocator.destroy(output);
|
||||
}
|
||||
|
||||
var seat_it = wm.seats.iterator(.forward);
|
||||
while (seat_it.next()) |seat| {
|
||||
seat.seat_v1.destroy();
|
||||
seat.link.remove();
|
||||
utils.allocator.destroy(seat);
|
||||
}
|
||||
|
||||
utils.allocator.destroy(wm);
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +90,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;
|
||||
|
|
@ -84,11 +108,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
|
||||
|
|
@ -96,25 +120,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,10 +146,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;
|
||||
}
|
||||
|
|
@ -140,13 +164,14 @@ 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.
|
||||
@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);
|
||||
|
|
@ -156,12 +181,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| {
|
||||
|
|
@ -176,9 +195,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| {
|
||||
|
|
@ -198,26 +225,38 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
|
|||
}
|
||||
}
|
||||
window_manager_v1.renderFinish();
|
||||
log.debug("render end==================", .{});
|
||||
},
|
||||
.output => |ev| {
|
||||
log.debug("3", .{});
|
||||
var output = utils.allocator.create(Output) catch @panic("out-of-memory; exiting.");
|
||||
// TODO: Support multi-output
|
||||
var output = utils.allocator.create(Output) catch @panic("Out of memory");
|
||||
output.init(context, ev.id);
|
||||
wm.outputs.append(output);
|
||||
},
|
||||
.seat => |ev| {
|
||||
log.debug("4", .{});
|
||||
var seat = utils.allocator.create(Seat) catch @panic("out-of-memory; exiting.");
|
||||
seat.init(context, ev.id);
|
||||
// TODO: Support multi-seat (maybe ?)
|
||||
const river_seat_v1 = ev.id;
|
||||
const river_seat_v1_version = river_seat_v1.getVersion();
|
||||
if (river_seat_v1_version < MIN_RIVER_SEAT_V1_VERSION) {
|
||||
@branchHint(.cold); // If we're in here, the program is exiting anyways
|
||||
utils.versionNotSupported(river.SeatV1, river_seat_v1_version, MIN_RIVER_SEAT_V1_VERSION);
|
||||
}
|
||||
|
||||
var seat = utils.allocator.create(Seat) catch @panic("Out of memory");
|
||||
seat.init(context, river_seat_v1);
|
||||
wm.seats.append(seat);
|
||||
},
|
||||
.window => |ev| {
|
||||
log.debug("5", .{});
|
||||
var window = utils.allocator.create(Window) catch @panic("out-of-memory; exiting.");
|
||||
var window = utils.allocator.create(Window) catch @panic("Out of memory");
|
||||
window.init(context, ev.id);
|
||||
wm.windows.append(window);
|
||||
|
||||
// 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_manage.pending_focus = .{ .window = window };
|
||||
seat.pending_manage.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)});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2025-2026 Ben Buhse <me@benbuhse.email>
|
||||
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -49,33 +49,46 @@ 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[0], 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_manage.pending_focus = .{ .window = window };
|
||||
seat.pending_manage.should_warp_pointer = true;
|
||||
} else {
|
||||
seat.pending_manage.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_manage.pending_focus = .{ .window = window };
|
||||
seat.pending_manage.should_warp_pointer = true;
|
||||
} else {
|
||||
seat.pending_manage.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;
|
||||
window.pending_manage.fullscreen = !window.fullscreen;
|
||||
// context.wm.window_manager_v1.manageDirty();
|
||||
},
|
||||
.close_window => {
|
||||
const seat = context.wm.seats.first() orelse return;
|
||||
|
|
@ -113,7 +126,13 @@ pub fn create(context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) !*XkbBin
|
|||
}
|
||||
|
||||
pub fn destroy(xkb_bindings: *XkbBindings) void {
|
||||
xkb_bindings.destroy();
|
||||
var it = xkb_bindings.bindings.iterator(.forward);
|
||||
while (it.next()) |binding| {
|
||||
binding.link.remove();
|
||||
binding.xkb_binding_v1.destroy();
|
||||
utils.allocator.destroy(binding);
|
||||
}
|
||||
utils.allocator.destroy(xkb_bindings);
|
||||
}
|
||||
|
||||
pub fn addBinding(xkb_bindings: *XkbBindings, river_seat_v1: *river.SeatV1, keysym: u32, modifiers: river.SeatV1.Modifiers, command: Command) void {
|
||||
|
|
@ -122,7 +141,7 @@ pub fn addBinding(xkb_bindings: *XkbBindings, river_seat_v1: *river.SeatV1, keys
|
|||
return;
|
||||
};
|
||||
|
||||
const xkb_binding = utils.allocator.create(XkbBinding) catch @panic("out-of-memory");
|
||||
const xkb_binding = utils.allocator.create(XkbBinding) catch @panic("Out of memory");
|
||||
xkb_binding.init(xkb_binding_v1, command, xkb_bindings.context);
|
||||
xkb_bindings.bindings.append(xkb_binding);
|
||||
|
||||
|
|
|
|||
21
src/main.zig
21
src/main.zig
|
|
@ -11,7 +11,7 @@ const Globals = struct {
|
|||
|
||||
pub fn main() !void {
|
||||
const wayland_display_var = try utils.allocator.dupeZ(u8, process.getEnvVarOwned(utils.allocator, "WAYLAND_DISPLAY") catch {
|
||||
log.err("Error getting WAYLAND_DISPLAY environment variable. Exiing", .{});
|
||||
log.err("Error getting WAYLAND_DISPLAY environment variable. Exiting", .{});
|
||||
std.posix.exit(1);
|
||||
});
|
||||
defer utils.allocator.free(wayland_display_var);
|
||||
|
|
@ -33,9 +33,9 @@ pub fn main() !void {
|
|||
std.posix.exit(1);
|
||||
}
|
||||
|
||||
const wl_compositor = globals.wl_compositor orelse interfaceNotAdvertised(wl.Compositor);
|
||||
const river_window_manager_v1 = globals.river_window_manager_v1 orelse interfaceNotAdvertised(river.WindowManagerV1);
|
||||
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse interfaceNotAdvertised(river.XkbBindingsV1);
|
||||
const wl_compositor = globals.wl_compositor orelse utils.interfaceNotAdvertised(wl.Compositor);
|
||||
const river_window_manager_v1 = globals.river_window_manager_v1 orelse utils.interfaceNotAdvertised(river.WindowManagerV1);
|
||||
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse utils.interfaceNotAdvertised(river.XkbBindingsV1);
|
||||
|
||||
const context = try Context.create(
|
||||
wl_display,
|
||||
|
|
@ -62,7 +62,7 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
switch (event) {
|
||||
.global => |ev| {
|
||||
if (mem.orderZ(u8, ev.interface, wl.Compositor.interface.name) == .eq) {
|
||||
if (ev.version < 4) versionNotSupported(wl.Compositor, ev.version, 4);
|
||||
if (ev.version < 4) utils.versionNotSupported(wl.Compositor, ev.version, 4);
|
||||
globals.wl_compositor = registry.bind(ev.name, wl.Compositor, 4) catch |e| {
|
||||
log.err("Failed to bind to compositor: {any}", .{@errorName(e)});
|
||||
std.posix.exit(1);
|
||||
|
|
@ -84,17 +84,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Actually use this...
|
||||
fn interfaceNotAdvertised(comptime WaylandGlobal: type) noreturn {
|
||||
log.err("{s} not advertised. Exiting", .{WaylandGlobal.interface.name});
|
||||
std.posix.exit(1);
|
||||
}
|
||||
|
||||
fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need_version: u32) noreturn {
|
||||
log.err("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version });
|
||||
std.posix.exit(1);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const process = std.process;
|
||||
|
|
|
|||
|
|
@ -65,5 +65,19 @@ fn parseRgbaHelper(bytes: [4]u8) RiverColor {
|
|||
};
|
||||
}
|
||||
|
||||
/// Report that the given WaylandGlobal wasn't advertised and exit the program
|
||||
pub fn interfaceNotAdvertised(comptime WaylandGlobal: type) noreturn {
|
||||
log.err("{s} not advertised. Exiting", .{WaylandGlobal.interface.name});
|
||||
std.posix.exit(1);
|
||||
}
|
||||
|
||||
/// Report that the given WaylandGlobal was advertised but the support version was too low exit the program
|
||||
pub fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need_version: u32) noreturn {
|
||||
log.err("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version });
|
||||
std.posix.exit(1);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const fmt = std.fmt;
|
||||
|
||||
const log = std.log.scoped(.utils);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue