Files
zig-a-puzzle-a-day/src/main.zig
T
2026-03-08 16:25:14 +01:00

1024 lines
37 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const linux = std.os.linux;
const puzzle = @import("puzzle");
const c = @cImport({
@cInclude("string.h");
@cInclude("wayland-client-core.h");
@cInclude("xdg-shell.h");
@cInclude("linux/input-event-codes.h");
@cInclude("xkbcommon/xkbcommon.h");
});
const WlPointerEventMask = packed struct(u16) {
ENTER: bool = false,
LEAVE: bool = false,
MOTION: bool = false,
BUTTON: bool = false,
AXIS: bool = false,
FRAME: bool = false,
AXIS_SOURCE: bool = false,
AXIS_STOP: bool = false,
AXIS_DISCRETE: bool = false,
AXIS_VALUE120: bool = false,
AXIS_RELATIVE_DIRECTION: bool = false,
_: u5,
};
const WlPointerEvent = struct {
mask: WlPointerEventMask,
time: u32,
serial: u32,
button: u32,
state: u32,
axis_x: f64,
axis_y: f64,
surface_x: c.wl_fixed_t = 0,
surface_y: c.wl_fixed_t = 0,
};
const WaylandState = struct {
running: bool,
configured: bool,
compositor: ?*c.wl_compositor,
seat: ?*c.wl_seat,
shared_memory: ?*c.wl_shm,
xdg_wm_base: ?*c.xdg_wm_base,
surface: ?*c.wl_surface,
xkb_context: ?*c.xkb_context,
xkb_keymap: ?*c.xkb_keymap,
xkb_state: ?*c.xkb_state,
allocator: std.mem.Allocator,
resize: bool,
frame_ready: bool,
window_resized: bool,
window_width: i32,
window_height: i32,
pointer_event: WlPointerEvent,
app_input: puzzle.platform.AppInput,
};
const MemoryPool = struct {
pool: ?*c.wl_shm_pool,
data: []u8,
};
fn createShmFile(pool_size: usize) !std.posix.fd_t {
const name = "/wl_shm-oceanbox-puzzle-a-day";
const flags: std.os.linux.O = .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true };
const mode: std.os.linux.mode_t = 0o0600;
const result = std.c.shm_open(name, @bitCast(flags), mode);
if (result >= 0) {
std.debug.print("[Wayland] Created shm file {s}\n", .{name});
_ = std.c.shm_unlink(name);
} else {
std.debug.print("Error creating shm file {s}\n", .{name});
return error.ShmOpenError;
}
try std.posix.ftruncate(result, pool_size);
std.debug.print("[Wayland] ftruncate shm to size {d}B\n", .{pool_size});
return result;
}
fn createSharedMemoryPool(wl_state: WaylandState) !MemoryPool {
const stride = wl_state.window_width * 4;
const pool_size: usize = @intCast(stride * wl_state.window_height);
const fd: c_int = try createShmFile(pool_size);
const prot = linux.PROT.READ | std.os.linux.PROT.WRITE;
const flags: linux.MAP = .{ .TYPE = .SHARED };
const ret = try std.posix.mmap(null, pool_size, prot, flags, fd, 0);
const pool = c.wl_shm_create_pool(wl_state.shared_memory, fd, @intCast(pool_size));
_ = std.os.linux.close(fd);
return .{
.pool = pool,
.data = ret[0..@intCast(pool_size)],
};
}
fn wlFrameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, callback_data: u32) callconv(.c) void {
_ = callback_data;
var wl_state: *WaylandState = @ptrCast(@alignCast(data));
c.wl_callback_destroy(callback);
const new_callback: ?*c.wl_callback = c.wl_surface_frame(wl_state.surface);
_ = c.wl_callback_add_listener(new_callback, &frame_callback_listener, wl_state);
std.debug.print("[Wayland] Frame ready\n", .{});
wl_state.frame_ready = true;
}
const frame_callback_listener: c.wl_callback_listener = .{
.done = wlFrameHandleDone,
};
fn xdg_wm_base_handle_ping(data: ?*anyopaque, xdg_wm_base: ?*c.xdg_wm_base, serial: u32) callconv(.c) void {
_ = data;
std.debug.print("[Wayland] Pong.\n", .{});
c.xdg_wm_base_pong(xdg_wm_base, serial);
}
const xdg_wm_base_listener: c.xdg_wm_base_listener = .{
.ping = xdg_wm_base_handle_ping,
};
// configure: ?*const fn (?*anyopaque, ?*struct_xdg_surface, u32) callconv(.c) void = @import("std").mem.zeroes(?*const fn (?*anyopaque, ?*struct_xdg_surface, u32) callconv(.c) void),
fn xdg_surface_handle_configure(data: ?*anyopaque, xdg_surface: ?*c.xdg_surface, serial: u32) callconv(.c) void {
var wl_state: *WaylandState = @ptrCast(@alignCast(data));
c.xdg_surface_ack_configure(xdg_surface, serial);
// TODO: Ready to resize?
if (wl_state.window_resized) {
wl_state.resize = true;
}
wl_state.configured = true;
}
const xdg_surface_listener: c.xdg_surface_listener = .{
.configure = xdg_surface_handle_configure,
};
fn xdg_toplevel_handle_configure(
data: ?*anyopaque,
xdg_toplevel: ?*c.xdg_toplevel,
width: i32,
height: i32,
names: [*c]c.wl_array,
) callconv(.c) void {
var wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = xdg_toplevel;
_ = names;
if (width != 0 and height != 0) {
std.debug.print("[Wayland] xdg toplevel configure: w = {d}, h = {d}.\n", .{ width, height });
wl_state.window_width = width;
wl_state.window_height = height;
wl_state.window_resized = true;
}
}
fn xdg_toplevel_handle_close(data: ?*anyopaque, xdg_toplevel: ?*c.xdg_toplevel) callconv(.c) void {
var wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = xdg_toplevel;
std.debug.print("Close.\n", .{});
wl_state.running = false;
}
fn xdg_toplevel_handle_configure_bounds(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, width: i32, height: i32) callconv(.c) void {
_ = data;
_ = toplevel;
std.debug.print("[Wayland] xdg toplevel configure bounds: w = {d}, h = {d}.\n", .{ width, height });
}
fn xdg_toplevel_handle_wm_capabilities(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, names: [*c]c.wl_array) callconv(.c) void {
_ = data;
_ = toplevel;
_ = names;
std.debug.print("[Wayland] xdg toplevel wm capabilities.\n", .{});
}
const xdg_toplevel_listener: c.xdg_toplevel_listener = .{
.configure = xdg_toplevel_handle_configure,
.close = xdg_toplevel_handle_close,
.configure_bounds = xdg_toplevel_handle_configure_bounds,
.wm_capabilities = xdg_toplevel_handle_wm_capabilities,
};
/// enter event
///
/// Notification that this seat's pointer is focused on a certain
/// surface.
///
/// When a seat's focus enters a surface, the pointer image is
/// undefined and a client should respond to this event by setting
/// an appropriate pointer image with the set_cursor request.
/// @param serial serial number of the enter event
/// @param surface surface entered by the pointer
/// @param surface_x surface-local x coordinate
/// @param surface_y surface-local y coordinate
fn wlPointerHandleEnter(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, surface: ?*c.wl_surface, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = wl_pointer;
_ = surface;
wl_state.pointer_event.mask.ENTER = true;
wl_state.pointer_event.serial = serial;
wl_state.pointer_event.surface_x = surface_x;
wl_state.pointer_event.surface_y = surface_y;
}
/// leave event
///
/// Notification that this seat's pointer is no longer focused on
/// a certain surface.
///
/// The leave notification is sent before the enter notification for
/// the new focus.
/// @param serial serial number of the leave event
/// @param surface surface left by the pointer
fn wlPointerHandleLeave(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, surface: ?*c.wl_surface) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = wl_pointer;
_ = surface;
wl_state.pointer_event.mask.LEAVE = true;
wl_state.pointer_event.serial = serial;
}
/// pointer motion event
///
/// Notification of pointer location change. The arguments
/// surface_x and surface_y are the location relative to the focused
/// surface.
/// @param time timestamp with millisecond granularity
/// @param surface_x surface-local x coordinate
/// @param surface_y surface-local y coordinate
fn wlPointerHandleMotion(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = wl_pointer;
wl_state.pointer_event.mask.MOTION = true;
wl_state.pointer_event.time = time;
wl_state.pointer_event.surface_x = surface_x;
wl_state.pointer_event.surface_y = surface_y;
}
/// pointer button event
///
/// Mouse button click and release notifications.
///
/// The location of the click is given by the last motion or enter
/// event. The time argument is a timestamp with millisecond
/// granularity, with an undefined base.
///
/// The button is a button code as defined in the Linux kernel's
/// linux/input-event-codes.h header file, e.g. BTN_LEFT.
///
/// Any 16-bit button code value is reserved for future additions to
/// the kernel's event code list. All other button codes above
/// 0xFFFF are currently undefined but may be used in future
/// versions of this protocol.
/// @param serial serial number of the button event
/// @param time timestamp with millisecond granularity
/// @param button button that produced the event
/// @param state physical state of the button
fn wlPointerHandleButton(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, time: u32, button: u32, state: u32) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = wl_pointer;
wl_state.pointer_event.mask.BUTTON = true;
wl_state.pointer_event.time = time;
wl_state.pointer_event.serial = serial;
wl_state.pointer_event.button = button;
wl_state.pointer_event.state = state;
}
/// axis event
///
/// Scroll and other axis notifications.
///
/// For scroll events (vertical and horizontal scroll axes), the
/// value parameter is the length of a vector along the specified
/// axis in a coordinate space identical to those of motion events,
/// representing a relative movement along the specified axis.
///
/// For devices that support movements non-parallel to axes multiple
/// axis events will be emitted.
///
/// When applicable, for example for touch pads, the server can
/// choose to emit scroll events where the motion vector is
/// equivalent to a motion event vector.
///
/// When applicable, a client can transform its content relative to
/// the scroll distance.
/// @param time timestamp with millisecond granularity
/// @param axis axis type
/// @param value length of vector in surface-local coordinate space
fn wlPointerHandleAxis(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, axis: u32, value: c.wl_fixed_t) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
_ = wl_pointer;
wl_state.pointer_event.mask.AXIS = true;
wl_state.pointer_event.time = time;
if (axis == 1) wl_state.pointer_event.axis_x = c.wl_fixed_to_double(value);
if (axis == 0) wl_state.pointer_event.axis_y = c.wl_fixed_to_double(value);
}
/// axis source event
///
/// Source information for scroll and other axes.
///
/// This event does not occur on its own. It is sent before a
/// wl_pointer.frame event and carries the source information for
/// all events within that frame.
///
/// The source specifies how this event was generated. If the source
/// is wl_pointer.axis_source.finger, a wl_pointer.axis_stop event
/// will be sent when the user lifts the finger off the device.
///
/// If the source is wl_pointer.axis_source.wheel,
/// wl_pointer.axis_source.wheel_tilt or
/// wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event
/// may or may not be sent. Whether a compositor sends an axis_stop
/// event for these sources is hardware-specific and
/// implementation-dependent; clients must not rely on receiving an
/// axis_stop event for these scroll sources and should treat scroll
/// sequences from these scroll sources as unterminated by default.
///
/// This event is optional. If the source is unknown for a
/// particular axis event sequence, no event is sent. Only one
/// wl_pointer.axis_source event is permitted per frame.
///
/// The order of wl_pointer.axis_discrete and wl_pointer.axis_source
/// is not guaranteed.
/// @param axis_source source of the axis event
/// @since 5
///
fn wlPointerHandleAxisSource(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis_source: u32) callconv(.c) void {
_ = data;
_ = wl_pointer;
_ = axis_source;
}
/// axis stop event
///
/// Stop notification for scroll and other axes.
///
/// For some wl_pointer.axis_source types, a wl_pointer.axis_stop
/// event is sent to notify a client that the axis sequence has
/// terminated. This enables the client to implement kinetic
/// scrolling. See the wl_pointer.axis_source documentation for
/// information on when this event may be generated.
///
/// Any wl_pointer.axis events with the same axis_source after this
/// event should be considered as the start of a new axis motion.
///
/// The timestamp is to be interpreted identical to the timestamp in
/// the wl_pointer.axis event. The timestamp value may be the same
/// as a preceding wl_pointer.axis event.
/// @param time timestamp with millisecond granularity
/// @param axis the axis stopped with this event
/// @since 5
fn wlPointerHandleAxisStop(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, axis: u32) callconv(.c) void {
_ = data;
_ = wl_pointer;
_ = time;
_ = axis;
}
/// axis click event
///
/// Discrete step information for scroll and other axes.
///
/// This event carries the axis value of the wl_pointer.axis event
/// in discrete steps (e.g. mouse wheel clicks).
///
/// This event is deprecated with wl_pointer version 8 - this event
/// is not sent to clients supporting version 8 or later.
///
/// This event does not occur on its own, it is coupled with a
/// wl_pointer.axis event that represents this axis value on a
/// continuous scale. The protocol guarantees that each
/// axis_discrete event is always followed by exactly one axis event
/// with the same axis number within the same wl_pointer.frame. Note
/// that the protocol allows for other events to occur between the
/// axis_discrete and its coupled axis event, including other
/// axis_discrete or axis events. A wl_pointer.frame must not
/// contain more than one axis_discrete event per axis type.
///
/// This event is optional; continuous scrolling devices like
/// two-finger scrolling on touchpads do not have discrete steps and
/// do not generate this event.
///
/// The discrete value carries the directional information. e.g. a
/// value of -2 is two steps towards the negative direction of this
/// axis.
///
/// The axis number is identical to the axis number in the
/// associated axis event.
///
/// The order of wl_pointer.axis_discrete and wl_pointer.axis_source
/// is not guaranteed.
/// @param axis axis type
/// @param discrete number of steps
/// @since 5
/// @deprecated Deprecated since version 8
fn wlPointerHandleAxisDiscrete(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, discrete: i32) callconv(.c) void {
_ = data;
_ = wl_pointer;
_ = axis;
_ = discrete;
}
/// axis high-resolution scroll event
///
/// Discrete high-resolution scroll information.
///
/// This event carries high-resolution wheel scroll information,
/// with each multiple of 120 representing one logical scroll step
/// (a wheel detent). For example, an axis_value120 of 30 is one
/// quarter of a logical scroll step in the positive direction, a
/// value120 of -240 are two logical scroll steps in the negative
/// direction within the same hardware event. Clients that rely on
/// discrete scrolling should accumulate the value120 to multiples
/// of 120 before processing the event.
///
/// The value120 must not be zero.
///
/// This event replaces the wl_pointer.axis_discrete event in
/// clients supporting wl_pointer version 8 or later.
///
/// Where a wl_pointer.axis_source event occurs in the same
/// wl_pointer.frame, the axis source applies to this event.
///
/// The order of wl_pointer.axis_value120 and wl_pointer.axis_source
/// is not guaranteed.
/// @param axis axis type
/// @param value120 scroll distance as fraction of 120
/// @since 8
fn wlPointerHandleAxisValue120(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, value120: i32) callconv(.c) void {
_ = data;
_ = wl_pointer;
_ = axis;
_ = value120;
}
/// axis relative physical direction event
///
/// Relative directional information of the entity causing the
/// axis motion.
///
/// For a wl_pointer.axis event, the
/// wl_pointer.axis_relative_direction event specifies the movement
/// direction of the entity causing the wl_pointer.axis event. For
/// example: - if a user's fingers on a touchpad move down and this
/// causes a wl_pointer.axis vertical_scroll down event, the
/// physical direction is 'identical' - if a user's fingers on a
/// touchpad move down and this causes a wl_pointer.axis
/// vertical_scroll up scroll up event ('natural scrolling'), the
/// physical direction is 'inverted'.
///
/// A client may use this information to adjust scroll motion of
/// components. Specifically, enabling natural scrolling causes the
/// content to change direction compared to traditional scrolling.
/// Some widgets like volume control sliders should usually match
/// the physical direction regardless of whether natural scrolling
/// is active. This event enables clients to match the scroll
/// direction of a widget to the physical direction.
///
/// This event does not occur on its own, it is coupled with a
/// wl_pointer.axis event that represents this axis value. The
/// protocol guarantees that each axis_relative_direction event is
/// always followed by exactly one axis event with the same axis
/// number within the same wl_pointer.frame. Note that the protocol
/// allows for other events to occur between the
/// axis_relative_direction and its coupled axis event.
///
/// The axis number is identical to the axis number in the
/// associated axis event.
///
/// The order of wl_pointer.axis_relative_direction,
/// wl_pointer.axis_discrete and wl_pointer.axis_source is not
/// guaranteed.
/// @param axis axis type
/// @param direction physical direction relative to axis motion
/// @since 9
fn wlPointerHandleAxisRelativeDirection(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, direction: u32) callconv(.c) void {
_ = data;
_ = wl_pointer;
_ = axis;
_ = direction;
}
/// end of a pointer event sequence
///
/// Indicates the end of a set of events that logically belong
/// together. A client is expected to accumulate the data in all
/// events within the frame before proceeding.
///
/// All wl_pointer events before a wl_pointer.frame event belong
/// logically together. For example, in a diagonal scroll motion the
/// compositor will send an optional wl_pointer.axis_source event,
/// two wl_pointer.axis events (horizontal and vertical) and finally
/// a wl_pointer.frame event. The client may use this information to
/// calculate a diagonal vector for scrolling.
///
/// When multiple wl_pointer.axis events occur within the same
/// frame, the motion vector is the combined motion of all events.
/// When a wl_pointer.axis and a wl_pointer.axis_stop event occur
/// within the same frame, this indicates that axis movement in one
/// axis has stopped but continues in the other axis. When multiple
/// wl_pointer.axis_stop events occur within the same frame, this
/// indicates that these axes stopped in the same instance.
///
/// A wl_pointer.frame event is sent for every logical event group,
/// even if the group only contains a single wl_pointer event.
/// Specifically, a client may get a sequence: motion, frame,
/// button, frame, axis, frame, axis_stop, frame.
///
/// The wl_pointer.enter and wl_pointer.leave events are logical
/// events generated by the compositor and not the hardware. These
/// events are also grouped by a wl_pointer.frame. When a pointer
/// moves from one surface to another, a compositor should group the
/// wl_pointer.leave event within the same wl_pointer.frame.
/// However, a client must not rely on wl_pointer.leave and
/// wl_pointer.enter being in the same wl_pointer.frame.
/// Compositor-specific policies may require the wl_pointer.leave
/// and wl_pointer.enter event being split across multiple
/// wl_pointer.frame groups.
/// @since 5
fn wlPointerHandleFrame(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer) callconv(.c) void {
_ = wl_pointer;
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
// std.debug.print("[Wayland] Pointer frame\n", .{});
if (wl_state.pointer_event.mask.ENTER) {
wl_state.app_input.mouse_present = true;
}
if (wl_state.pointer_event.mask.LEAVE) {
wl_state.app_input.mouse_present = false;
}
if (wl_state.pointer_event.mask.AXIS) {
std.debug.print(
"[Wayland] Pointer frame axis: x = {d}, y = {d}\n",
.{ wl_state.pointer_event.axis_x, wl_state.pointer_event.axis_y },
);
wl_state.app_input.scroll_y = wl_state.pointer_event.axis_y;
} else {
wl_state.app_input.scroll_y = 0.0;
}
if (wl_state.pointer_event.mask.MOTION) {
// std.debug.print("[Wayland] Pointer frame motion\n", .{});
wl_state.app_input.mouse_x = c.wl_fixed_to_double(wl_state.pointer_event.surface_x);
wl_state.app_input.mouse_y = c.wl_fixed_to_double(wl_state.pointer_event.surface_y);
}
if (wl_state.pointer_event.mask.BUTTON) {
// std.debug.print("[Wayland] Pointer frame button\n", .{});
if (wl_state.pointer_event.button == c.BTN_LEFT) {
// std.debug.print("[Wayland] Pointer frame button left\n", .{});
if (wl_state.pointer_event.state == c.WL_POINTER_BUTTON_STATE_PRESSED) {
// std.debug.print("[Wayland] Pointer frame button left down\n", .{});
wl_state.app_input.mouse_left_down = true;
} else if (wl_state.pointer_event.state == c.WL_POINTER_BUTTON_STATE_RELEASED) {
// std.debug.print("[Wayland] Pointer frame button left up\n", .{});
wl_state.app_input.mouse_left_down = false;
}
}
}
wl_state.pointer_event = std.mem.zeroInit(WlPointerEvent, .{});
}
const wl_pointer_listener: c.wl_pointer_listener = .{
.enter = wlPointerHandleEnter,
.leave = wlPointerHandleLeave,
.motion = wlPointerHandleMotion,
.button = wlPointerHandleButton,
.axis = wlPointerHandleAxis,
.axis_source = wlPointerHandleAxisSource,
.axis_stop = wlPointerHandleAxisStop,
.axis_discrete = wlPointerHandleAxisDiscrete,
.axis_value120 = wlPointerHandleAxisValue120,
.axis_relative_direction = wlPointerHandleAxisRelativeDirection,
.frame = wlPointerHandleFrame,
};
fn wlKeyboardHandleKeymap(
data: ?*anyopaque,
wl_keyboard: ?*c.wl_keyboard,
format: u32,
fd: i32,
size: u32,
) callconv(.c) void {
_ = wl_keyboard;
std.debug.assert(format == c.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
const map_shm: []align(4096) const u8 = std.posix.mmap(null, size, linux.PROT.READ, .{ .TYPE = .SHARED }, fd, 0) catch {
std.debug.panic("[Wayland] Keyboard keymap handler failed mmap", .{});
};
const keymap: ?*c.xkb_keymap = c.xkb_keymap_new_from_string(wl_state.xkb_context, @ptrCast(map_shm), c.XKB_KEYMAP_FORMAT_TEXT_V1, c.XKB_KEYMAP_COMPILE_NO_FLAGS);
std.posix.munmap(map_shm);
std.posix.close(fd);
const xkb_state: ?*c.xkb_state = c.xkb_state_new(keymap);
// NOTE: Unmap existing ones
if (wl_state.xkb_keymap != null) c.xkb_keymap_unref(wl_state.xkb_keymap);
if (wl_state.xkb_state != null) c.xkb_state_unref(wl_state.xkb_state);
wl_state.xkb_keymap = keymap;
wl_state.xkb_state = xkb_state;
}
fn wlKeyboardHandleEnter(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface, keys: [*c]c.wl_array) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = surface;
_ = keys;
}
fn wlKeyboardHandleLeave(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = surface;
}
fn wlKeyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, time: u32, key: u32, state: u32) callconv(.c) void {
_ = keyboard;
_ = serial;
_ = time;
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
var buf: [32:0]u8 = undefined;
const keycode: u32 = key + 8;
const sym: c.xkb_keysym_t = c.xkb_state_key_get_one_sym(wl_state.xkb_state, keycode);
_ = c.xkb_keysym_get_name(sym, &buf, buf.len);
const pressed = state == c.WL_KEYBOARD_KEY_STATE_PRESSED or state == c.WL_KEYBOARD_KEY_STATE_REPEATED;
std.debug.print("{s} {any}\n", .{ buf, pressed });
switch (sym) {
c.XKB_KEY_Escape => {
wl_state.running = false;
},
c.XKB_KEY_f => {
const already_pressed: bool = wl_state.app_input.key.f.down;
wl_state.app_input.key.f.down = pressed;
wl_state.app_input.key.f.pressing = pressed and already_pressed;
},
c.XKB_KEY_r => {
const already_pressed: bool = wl_state.app_input.key.r.down;
wl_state.app_input.key.r.down = pressed;
wl_state.app_input.key.r.pressing = pressed and already_pressed;
},
c.XKB_KEY_q => {
const already_pressed: bool = wl_state.app_input.key.q.down;
wl_state.app_input.key.q.down = pressed;
wl_state.app_input.key.q.pressing = pressed and already_pressed;
},
c.XKB_KEY_space => {
const already_pressed: bool = wl_state.app_input.key.space.down;
wl_state.app_input.key.space.down = pressed;
wl_state.app_input.key.space.pressing = pressed and already_pressed;
},
else => {},
}
}
fn wlKeyboardHandleModifiers(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, mods_depressed: u32, mods_latched: u32, mods_locked: u32, group: u32) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = mods_depressed;
_ = mods_latched;
_ = mods_locked;
_ = group;
}
fn wlKeyboardHandleRepeatInfo(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, rate: i32, delay: i32) callconv(.c) void {
_ = data;
_ = keyboard;
_ = rate;
_ = delay;
}
const wl_keyboard_listener: c.wl_keyboard_listener = .{
.keymap = wlKeyboardHandleKeymap,
.enter = wlKeyboardHandleEnter,
.leave = wlKeyboardHandleLeave,
.key = wlKeyboardHandleKey,
.modifiers = wlKeyboardHandleModifiers,
.repeat_info = wlKeyboardHandleRepeatInfo,
};
fn wlSeatHandleCapabilities(data: ?*anyopaque, wl_seat: ?*c.wl_seat, capabilities: u32) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
if (capabilities & c.WL_SEAT_CAPABILITY_POINTER == c.WL_SEAT_CAPABILITY_POINTER) {
std.debug.print("[Wayland] wl_seat: got pointer capability\n", .{});
const pointer: ?*c.wl_pointer = c.wl_seat_get_pointer(wl_seat);
_ = c.wl_pointer_add_listener(pointer, &wl_pointer_listener, wl_state);
}
if (capabilities & c.WL_SEAT_CAPABILITY_KEYBOARD == c.WL_SEAT_CAPABILITY_KEYBOARD) {
std.debug.print("[Wayland] wl_seat: got keyboard capability\n", .{});
const keyboard = c.wl_seat_get_keyboard(wl_seat);
_ = c.wl_keyboard_add_listener(keyboard, &wl_keyboard_listener, wl_state);
}
}
fn wlSeatHandleName(data: ?*anyopaque, wl_seat: ?*c.wl_seat, name: [*c]const u8) callconv(.c) void {
_ = data;
_ = wl_seat;
std.debug.print("[Wayland] wl_seat name {s}\n", .{name});
}
const wl_seat_listener: c.wl_seat_listener = .{
.capabilities = wlSeatHandleCapabilities,
.name = wlSeatHandleName,
};
fn registry_handle_global(
data: ?*anyopaque,
registry: ?*c.wl_registry,
name: u32,
interface: [*c]const u8,
version: u32,
) callconv(.c) void {
var wl_state: *WaylandState = @ptrCast(@alignCast(data));
if (c.strcmp(interface, c.wl_compositor_interface.name) == 0) {
std.debug.print("[Wayland] Binding to interface {s} version {d}.\n", .{ interface, version });
wl_state.compositor = @ptrCast(@alignCast(c.wl_registry_bind(registry, name, &c.wl_compositor_interface, version)));
} else if (c.strcmp(interface, c.wl_shm_interface.name) == 0) {
std.debug.print("[Wayland] Binding to interface {s} version {d}.\n", .{ interface, version });
wl_state.shared_memory = @ptrCast(@alignCast(c.wl_registry_bind(registry, name, &c.wl_shm_interface, version)));
} else if (c.strcmp(interface, c.wl_seat_interface.name) == 0) {
std.debug.print("[Wayland] Binding to interface {s} version {d}.\n", .{ interface, version });
wl_state.seat = @ptrCast(@alignCast(c.wl_registry_bind(registry, name, &c.wl_seat_interface, version)));
_ = c.wl_seat_add_listener(wl_state.seat, &wl_seat_listener, wl_state);
} else if (c.strcmp(interface, c.xdg_wm_base_interface.name) == 0) {
std.debug.print("[Wayland] Binding to interface {s} version {d}.\n", .{ interface, version });
wl_state.xdg_wm_base = @ptrCast(@alignCast(c.wl_registry_bind(registry, name, &c.xdg_wm_base_interface, version)));
_ = c.xdg_wm_base_add_listener(wl_state.xdg_wm_base, &xdg_wm_base_listener, wl_state);
}
}
const registry_listener: c.wl_registry_listener = .{
.global = registry_handle_global,
};
pub fn main() u8 {
const display = c.wl_display_connect(null);
if (display == null) {
std.debug.print("Error connecting to wayland display.\n", .{});
return 1;
}
const wl_fd = c.wl_display_get_fd(display);
std.debug.print("[Wayland] Connection established.\n", .{});
var wl_state: WaylandState = undefined;
wl_state.xkb_context = null;
wl_state.xkb_state = null;
wl_state.xkb_keymap = null;
const registry = c.wl_display_get_registry(display);
if (registry == null) {
std.debug.print("Error getting registry.\n", .{});
return 1;
}
_ = c.wl_registry_add_listener(registry, &registry_listener, &wl_state);
wl_state.xkb_context = c.xkb_context_new(c.XKB_CONTEXT_NO_FLAGS);
_ = c.wl_display_roundtrip(display);
if (wl_state.compositor == null or wl_state.shared_memory == null or wl_state.seat == null) {
std.debug.print("ERROR: Wayland compositor, seat, or shm not bound.\n", .{});
return 1;
}
const wl_surface = c.wl_compositor_create_surface(wl_state.compositor);
if (wl_surface == null) {
std.debug.print("ERROR: Could not create surface.\n", .{});
return 1;
}
wl_state.surface = wl_surface;
const xdg_surface = c.xdg_wm_base_get_xdg_surface(wl_state.xdg_wm_base, wl_surface);
if (xdg_surface == null) {
std.debug.print("ERROR: Could not create xdg surface.\n", .{});
return 1;
}
_ = c.xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, &wl_state);
const xdg_toplevel = c.xdg_surface_get_toplevel(xdg_surface);
if (xdg_toplevel == null) {
std.debug.print("ERROR: Could not get xdg toplevel\n", .{});
return 1;
}
_ = c.xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, &wl_state);
c.xdg_toplevel_set_title(xdg_toplevel, "Zig A-Puzzle-A-Day");
c.wl_surface_commit(wl_surface);
while (c.wl_display_dispatch(display) != -1 and !wl_state.configured) {}
_ = c.wl_display_roundtrip(display);
c.wl_surface_commit(wl_surface);
std.debug.print("[Wayland] Surface commited.\n", .{});
wl_state.running = true;
wl_state.resize = true;
wl_state.window_width = 800;
wl_state.window_height = 600;
var shared_memory_pool: ?*c.wl_shm_pool = undefined;
var shared_memory_pool_data: []u8 = undefined;
var wl_buffer: *c.wl_buffer = undefined;
const monitor_update_hz: f64 = 144;
const game_update_hz: f64 = monitor_update_hz;
const frame_target_time_s: f64 = 1.0 / game_update_hz;
const frame_target_time_ms: f64 = std.time.ms_per_s * frame_target_time_s;
const frame_target_time_ns: u64 = std.time.ns_per_ms * @as(u64, @intFromFloat(frame_target_time_ms));
// Memory
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();
wl_state.allocator = allocator;
const app_permanent_storage = allocator.alloc(u8, puzzle.platform.megaBytes(8)) catch {
std.debug.print("[Wayland] ERROR: OOM!\n", .{});
return 1;
};
var app_memory: puzzle.platform.AppMemory = .{
.initialized = false,
.permanent_storage = app_permanent_storage,
};
var frame_callback_added = false;
var timer = std.time.Timer.start() catch {
std.debug.print("[Wayland] ERROR: timer not supported.\n", .{});
return 1;
};
var fds: [1]std.os.linux.pollfd = .{
.{
.fd = wl_fd,
.events = std.posix.POLL.IN,
.revents = 0,
},
};
while (wl_state.running) {
outer: while (true) {
const cont = std.posix.poll(&fds, 0) catch |err| {
switch (err) {
error.Unexpected => {
std.debug.print("Poll error: Unexpected.\n", .{});
},
error.SystemResources => {
std.debug.print("Poll error: SystemResources: Kernel has no space for table allocations.\n", .{});
},
else => {
std.debug.print("Poll error: network stuff idk.\n", .{});
},
}
return 0;
};
if (cont == 0) break;
for (fds) |fd| {
if (fd.revents & std.posix.POLL.IN > 0) {
const ret = c.wl_display_dispatch(display);
if (ret < 0) {
std.debug.print("Dispatch error {d}.\n", .{ret});
wl_state.running = false;
break :outer;
}
}
}
}
if (wl_state.resize) {
const new_pool = createSharedMemoryPool(wl_state) catch {
std.debug.print("[Wayland] Error creating memory pool!\n", .{});
wl_state.running = false;
break;
};
std.debug.print(
"[Wayland] Reizing window to {d}x{d} = {d}B.\n",
.{ wl_state.window_width, wl_state.window_height, new_pool.data.len },
);
shared_memory_pool = new_pool.pool;
shared_memory_pool_data = new_pool.data;
const stride = wl_state.window_width * 4;
const buffer = c.wl_shm_pool_create_buffer(
shared_memory_pool,
0,
wl_state.window_width,
wl_state.window_height,
stride,
c.WL_SHM_FORMAT_ARGB8888,
);
if (buffer != null) {
wl_buffer = buffer.?;
} else {
std.debug.print("[Wayland] Error creating buffer!\n", .{});
wl_state.running = false;
break;
}
wl_state.resize = false;
wl_state.window_resized = false;
}
const render_start_time: u64 = timer.read();
if (wl_state.frame_ready) {
wl_state.app_input.delta_time = frame_target_time_s;
const app_offscreen_buffer: puzzle.platform.OffscreenBuffer = .{
.width = wl_state.window_width,
.height = wl_state.window_height,
.stride = wl_state.window_width * 4,
.data = shared_memory_pool_data,
};
puzzle.updateAndRender(&app_memory, app_offscreen_buffer, &wl_state.app_input);
}
const render_end_time: u64 = timer.read();
const render_time = render_end_time - render_start_time;
const render_time_ms: f64 = @as(f64, @floatFromInt(render_time)) / @as(f64, @floatFromInt(std.time.ns_per_ms));
var sleep_time: u64 = frame_target_time_ns;
if (render_time < frame_target_time_ns) {
sleep_time -= render_time;
std.posix.nanosleep(0, sleep_time);
} else {
sleep_time = 0;
std.debug.print("MISSED FRAME\n", .{});
}
if (wl_state.frame_ready) {
c.wl_surface_attach(wl_surface, wl_buffer, 0, 0);
c.wl_surface_damage_buffer(wl_surface, 0, 0, wl_state.window_width, wl_state.window_height);
c.wl_surface_commit(wl_surface);
wl_state.frame_ready = false;
}
_ = c.wl_display_roundtrip(display);
if (!frame_callback_added) {
const callback: ?*c.wl_callback = c.wl_surface_frame(wl_surface);
if (callback) |cb| {
const ret: c_int = c.wl_callback_add_listener(cb, &frame_callback_listener, &wl_state);
std.debug.print("[Wayland] Added frame callback with ret {d}.\n", .{ret});
}
frame_callback_added = true;
wl_state.frame_ready = true;
}
// std.posix.nanosleep(0, std.time.ns_per_ms * 32);
const current = timer.lap();
const current_ms = current / std.time.ns_per_ms;
const sleep_time_ms: f64 = @as(f64, @floatFromInt(sleep_time)) / @as(f64, @floatFromInt(std.time.ns_per_ms));
std.debug.print(
"Render: {d:.3}ms, Sleeping: {d:.3}ms, Frame: {d}ms.\n",
.{ render_time_ms, sleep_time_ms, current_ms },
);
}
return 0;
}