diff --git a/src/core.zig b/src/core.zig index db6c178..17ffd7d 100644 --- a/src/core.zig +++ b/src/core.zig @@ -160,7 +160,7 @@ pub const ShmPool = struct { return .{ .conn = conn, .id = id }; } - pub fn create_buffer(shm_pool: ShmPool, offset: i32, width: i32, height: i32, stride: i32, format: Shm.Format) u32 { + pub fn create_buffer(shm_pool: ShmPool, offset: i32, width: i32, height: i32, stride: i32, format: Shm.Format) !Buffer { const new_id = shm_pool.conn.id_pool.create(); try shm_pool.conn.send( Request, @@ -174,7 +174,7 @@ pub const ShmPool = struct { .format = format, } }, ); - return Surface.init(shm_pool.conn, new_id); + return Buffer.init(shm_pool.conn, new_id); } }; @@ -216,7 +216,7 @@ pub const Shm = struct { return .{ .conn = conn, .id = id }; } - pub fn create_pool(shm: *const Shm, pool_fd: u32, size: u32) ShmPool { + pub fn create_pool(shm: *const Shm, pool_fd: u32, size: u32) !ShmPool { const new_id = shm.conn.id_pool.create(); try shm.conn.send( Request, @@ -284,6 +284,31 @@ pub const Surface = struct { return .{ .conn = conn, .id = id }; } + pub fn attach(surface: Surface, buffer: Buffer, x: i32, y: i32) !void { + try surface.conn.send( + Request, + surface.id, + .{ .attach = .{ + .buffer = buffer.id, + .x = x, + .y = y, + } }, + ); + } + + pub fn damage(surface: Surface, x: i32, y: i32, width: i32, height: i32) !void { + try surface.conn.send( + Request, + surface.id, + .{ .damage = .{ + .x = x, + .y = y, + .width = width, + .height = height, + } }, + ); + } + pub fn commit(surface: Surface) !void { try surface.conn.send( Request, @@ -301,6 +326,13 @@ pub const Buffer = struct { pub const Event = union(enum) { release: void, }; + + conn: *Conn, + id: u32, + + pub fn init(conn: *Conn, id: u32) Buffer { + return .{ .conn = conn, .id = id }; + } }; pub const Seat = struct { @@ -344,6 +376,40 @@ pub const Seat = struct { pub fn init(conn: *Conn, id: u32) Seat { return .{ .conn = conn, .id = id }; } + + pub fn get_pointer(seat: Seat) !Pointer { + const new_id = seat.conn.id_pool.create(); + try seat.conn.send( + Request, + seat.id, + .{ .get_pointer = .{ + .new_id = new_id, + } }, + ); + return Pointer.init(seat.conn, new_id); + } + pub fn get_keyboard(seat: Seat) !Keyboard { + const new_id = seat.conn.id_pool.create(); + try seat.conn.send( + Request, + seat.id, + .{ .get_keyboard = .{ + .new_id = new_id, + } }, + ); + return Keyboard.init(seat.conn, new_id); + } + pub fn get_touch(seat: Seat) !Touch { + const new_id = seat.conn.id_pool.create(); + try seat.conn.send( + Request, + seat.id, + .{ .get_touch = .{ + .new_id = new_id, + } }, + ); + return Touch.init(seat.conn, new_id); + } }; pub const Pointer = struct { @@ -435,7 +501,7 @@ pub const Pointer = struct { conn: *Conn, id: u32, - pub fn init(conn: *Conn, id: u32) Seat { + pub fn init(conn: *Conn, id: u32) Pointer { return .{ .conn = conn, .id = id }; } }; @@ -492,7 +558,7 @@ pub const Keyboard = struct { conn: *Conn, id: u32, - pub fn init(conn: *Conn, id: u32) Seat { + pub fn init(conn: *Conn, id: u32) Keyboard { return .{ .conn = conn, .id = id }; } }; @@ -538,7 +604,7 @@ pub const Touch = struct { conn: *Conn, id: u32, - pub fn init(conn: *Conn, id: u32) Seat { + pub fn init(conn: *Conn, id: u32) Touch { return .{ .conn = conn, .id = id }; } }; diff --git a/src/main.zig b/src/main.zig index ccef19a..a805968 100644 --- a/src/main.zig +++ b/src/main.zig @@ -63,6 +63,7 @@ pub fn main() !void { var conn = try Conn.init(gpa, display_path); defer conn.deinit(); + // Register all globals var ctx = try Context.init(&conn); const compositor = try ctx.getGlobal(wayland.core.Compositor); @@ -75,58 +76,468 @@ pub fn main() !void { try surface.commit(); const display = try ctx.getGlobal(wayland.core.Display); - const registry_done = try display.sync(); const shm = try ctx.getGlobal(wayland.core.Shm); const seat = try ctx.getGlobal(wayland.core.Seat); - var done = false; - var surface_configured = false; - var seat_capabilties: ?wayland.core.Seat.Capability = null; - while (!done or !surface_configured) { + var app = App{ + .conn = &conn, + .ctx = &ctx, + .ally = gpa, + .callbacks = std.AutoHashMap(u32, App.Callback).init(gpa), + .state = .{ .init = .{} }, + .display = display, + .shm = shm, + .xdg_wmbase = xdg_wm_base, + .surface = surface, + .xdg_surface = xdg_surface, + .xdg_toplevel = xdg_toplevel, + }; + defer app.deinit(); + + try app.callbacks.put(seat.id, seatInitHandler); + try app.callbacks.put(xdg_toplevel.id, toplevelInitHandler); + try app.callbacks.put(xdg_surface.id, surfaceInitHandler); + try app.callbacks.put(display.id, displayHandler); + + while (app.state == .init) { const header, const body = try conn.recv(); - if (header.object_id == xdg_surface.id) { - const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body); - switch (event) { - .configure => |conf| { - try xdg_surface.ack_configure(conf.serial); - surface_configured = true; - }, - } - } else if (header.object_id == xdg_toplevel.id) { - const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body); - std.debug.print("<- {}\n", .{event}); - } else if (header.object_id == registry_done) { - done = true; - } else if (header.object_id == shm.id) { - const event = try wayland.deserialize(wayland.core.Shm.Event, header, body); - switch (event) { - .format => |format| std.debug.print("<- format {} {}\n", .{ format.format, std.zig.fmtEscapes(std.mem.asBytes(&format.format)) }), - } - } else if (header.object_id == seat.id) { - const event = try wayland.deserialize(wayland.core.Seat.Event, header, body); - switch (event) { - .capabilities => |capabilities| { - const cap: wayland.core.Seat.Capability = @bitCast(capabilities.capability); - std.debug.print("<- wl_seat.capabilties = {}\n", .{cap}); - seat_capabilties = cap; - }, - .name => |name| { - std.debug.print("<- wl_seat.name = {s}\n", .{name.name}); - }, - } - } else if (header.object_id == display.id) { - const event = try wayland.deserialize(wayland.core.Display.Event, header, body); - switch (event) { - .@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }), - .delete_id => |id| { - std.debug.print("id {} deleted\n", .{id}); - conn.id_pool.destroy(id.id); - }, - } + if (app.callbacks.get(header.object_id)) |callback| { + try callback(&app, header, body); } else { - std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(body)) }); + const typename = if (ctx.getGlobalObjectName(header.object_id)) |name| + name + else + "UNKNOWN"; + std.debug.print("{} ({s}) {x} \"{}\"\n", .{ + header.object_id, + typename, + header.size_and_opcode.opcode, + std.zig.fmtEscapes(std.mem.sliceAsBytes(body)), + }); + } + } + + const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool; + const width = if (app.state == .run_mobile) app.state.run_mobile.width else app.state.run_desktop.width; + const height = if (app.state == .run_mobile) app.state.run_mobile.height else app.state.run_desktop.height; + + // render + const canvas = try pool.getFramebuffer(.{ width, height }); + canvas.renderGradient(); + try canvas.attach(surface); + try app.callbacks.put(canvas.buffer.id, bufferHandler); + + while (app.state != .close) { + const header, const body = try conn.recv(); + + if (app.callbacks.get(header.object_id)) |callback| { + try callback(&app, header, body); + } else { + const typename = if (ctx.getGlobalObjectName(header.object_id)) |name| + name + else + "UNKNOWN"; + std.debug.print("{} ({s}) {x} \"{}\"\n", .{ + header.object_id, + typename, + header.size_and_opcode.opcode, + std.zig.fmtEscapes(std.mem.sliceAsBytes(body)), + }); } } } + +const App = struct { + conn: *Conn, + ctx: *Context, + ally: std.mem.Allocator, + callbacks: std.AutoHashMap(u32, Callback), + + display: wayland.core.Display, + shm: wayland.core.Shm, + surface: wayland.core.Surface, + xdg_wmbase: wayland.xdg.WmBase, + xdg_surface: wayland.xdg.Surface, + xdg_toplevel: wayland.xdg.Toplevel, + + state: union(enum) { + init: struct { + toplevel_config: ?SurfaceConfiguration = null, + surface_configured: bool = false, + seat_capabilities: ?wayland.core.Seat.Capability = null, + pointer: ?wayland.core.Pointer = null, + keyboard: ?wayland.core.Keyboard = null, + touch: ?wayland.core.Touch = null, + }, + run_mobile: struct { + width: u32, + height: u32, + pool: Pool, + touch: wayland.core.Touch, + }, + run_desktop: struct { + width: u32, + height: u32, + pool: Pool, + keyboard: wayland.core.Keyboard, + pointer: wayland.core.Pointer, + mouse: struct { + x: f32, + y: f32, + }, + }, + close, + }, + + const SurfaceConfiguration = struct { + width: u32, + height: u32, + }; + + const Callback = *const fn (*App, wayland.Header, []const u32) anyerror!void; + + fn deinit(app: *App) void { + app.callbacks.deinit(); + } + + fn checkInit(app: *App) !void { + std.debug.assert(app.state == .init); + const init = app.state.init; + if (!init.surface_configured) return; + if (init.seat_capabilities == null) return; + + app.callbacks.clearRetainingCapacity(); + try app.callbacks.put(app.display.id, displayHandler); + + if (init.touch != null and init.keyboard == null) { + // mobile + app.state = .{ .run_mobile = .{ + .width = init.toplevel_config.?.width, + .height = init.toplevel_config.?.height, + .pool = try Pool.init(app.ally, app.shm), + .touch = init.touch.?, + } }; + try app.callbacks.put(app.xdg_wmbase.id, wmbaseHandler); + try app.callbacks.put(app.xdg_toplevel.id, toplevelHandler); + // try app.callbacks.put(app.state.run_desktop.touch.id, touchMobileHandler); + } else if (init.keyboard != null and init.pointer != null) { + // desktop + app.state = .{ .run_desktop = .{ + .width = init.toplevel_config.?.width, + .height = init.toplevel_config.?.height, + .pool = try Pool.init(app.ally, app.shm), + .keyboard = init.keyboard.?, + .pointer = init.pointer.?, + .mouse = .{ .x = 0, .y = 0 }, + } }; + try app.callbacks.put(app.state.run_desktop.pointer.id, pointerDesktopHandler); + try app.callbacks.put(app.state.run_desktop.keyboard.id, keyboardDesktopHandler); + try app.callbacks.put(app.xdg_surface.id, surfaceDesktopHandler); + try app.callbacks.put(app.xdg_wmbase.id, wmbaseHandler); + try app.callbacks.put(app.xdg_toplevel.id, toplevelHandler); + } else { + @panic("no keyboard or touch"); + } + } +}; + +fn surfaceInitHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body); + switch (event) { + .configure => |conf| { + try app.xdg_surface.ack_configure(conf.serial); + std.debug.assert(app.state.init.toplevel_config != null); + app.state.init.surface_configured = true; + try app.checkInit(); + }, + } +} + +fn toplevelInitHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body); + switch (event) { + .configure => |conf| { + var width = conf.width; + var height = conf.height; + if (conf.width == 0) { + width = 128; + height = 128; + } + app.state.init.toplevel_config = .{ + .width = @intCast(width), + .height = @intCast(height), + }; + }, + else => { + std.log.info("toplevel event: {}", .{event}); + }, + } +} + +fn seatInitHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.core.Seat.Event, header, body); + const seat = wayland.core.Seat.init(app.conn, header.object_id); // TODO: pass into cb + switch (event) { + .capabilities => |capabilities| { + const caps: wayland.core.Seat.Capability = @bitCast(capabilities.capability); + app.state.init.seat_capabilities = caps; + + if (caps.pointer) { + app.state.init.pointer = try seat.get_pointer(); + } + if (caps.touch) { + app.state.init.touch = try seat.get_touch(); + } + if (caps.keyboard) { + app.state.init.keyboard = try seat.get_keyboard(); + } + + try app.checkInit(); + }, + .name => |name| { + std.debug.print("<- wl_seat.name = {s}\n", .{name.name}); + }, + } +} + +fn displayHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.core.Display.Event, header, body); + switch (event) { + .@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }), + .delete_id => |id| { + std.debug.print("id {} deleted\n", .{id}); + app.conn.id_pool.destroy(id.id); + }, + } +} + +const Pool = struct { + fd: std.os.fd_t, + shm: wayland.core.ShmPool, + mem: []u8, + file_length: usize, + allocationTable: std.AutoHashMap(u32, Buffer), + const Buffer = struct { + offset: usize, + len: usize, + width: usize = 0, + height: usize = 0, + }; + + // Memory to store 4k screen pixels + // const UPPER_BOUND = 33_177_600; + // Memory to store 8k screen pixels + // const UPPER_BOUND = 132_710_400; + + const UPPER_BOUND = (7680 * 4320 * 4); + + fn init(allocator: std.mem.Allocator, shm: wayland.core.Shm) !Pool { + const pool_fd_1 = try std.os.memfd_create("my-wayland-framebuffer-1", 0); + const pool_bytes_1 = try std.os.mmap( + null, + UPPER_BOUND, + std.os.PROT.READ | std.os.PROT.WRITE, + std.os.MAP.SHARED, + pool_fd_1, + 0, + ); + const shm1 = try shm.create_pool(@intCast(pool_fd_1), @intCast(pool_bytes_1.len)); + return Pool{ + .fd = pool_fd_1, + .shm = shm1, + .mem = pool_bytes_1, + .file_length = 0, + .allocationTable = std.AutoHashMap(u32, Buffer).init(allocator), + }; + } + + fn getFramebuffer(pool: *Pool, size: [2]u32) !Canvas { + var iter = pool.allocationTable.iterator(); + var first_offset: usize = std.math.maxInt(usize); + var total_len: usize = 0; + while (iter.next()) |entry| { + const offset = entry.value_ptr.offset; + const len = entry.value_ptr.len; + total_len += len; + if (offset < first_offset) { + if (first_offset != std.math.maxInt(usize)) { + total_len += first_offset - offset; + } + first_offset = offset; + } + } + const pixel_count = size[0] * size[1]; + const byte_count = pixel_count * @sizeOf(Pixel); + var alloc_offset: usize = 0; + if (byte_count < first_offset) { + alloc_offset = 0; + } else { + alloc_offset = first_offset + total_len; + } + const final_offset = alloc_offset + byte_count; + if (final_offset > pool.file_length) { + try std.os.ftruncate(pool.fd, final_offset); + pool.file_length = final_offset; + } + + const buffer = try pool.shm.create_buffer( + @intCast(alloc_offset), + @intCast(size[0]), + @intCast(size[1]), + @intCast(size[0] * @sizeOf(Pixel)), + .argb8888, + ); + + try pool.allocationTable.put(buffer.id, .{ + .offset = alloc_offset, + .len = byte_count, + .width = size[0], + .height = size[1], + }); + + return .{ + .pool = pool, + .buffer = buffer, + .fb = @as([*]Pixel, @ptrCast(pool.mem[alloc_offset..].ptr))[0..pixel_count], + .size = size, + }; + } + + fn freeFramebuffer(pool: *Pool, buffer_id: u32) !void { + std.debug.assert(pool.allocationTable.remove(buffer_id)); + } +}; + +const Canvas = struct { + pool: *Pool, + fb: []Pixel, + buffer: wayland.core.Buffer, + size: [2]u32, + + fn attach(canvas: Canvas, surface: wayland.core.Surface) !void { + try surface.attach(canvas.buffer, 0, 0); + try surface.damage(0, 0, std.math.maxInt(i32), std.math.maxInt(i32)); + try surface.commit(); + } + + fn renderGradient(canvas: Canvas) void { + const fb = canvas.fb; + const width = canvas.size[0]; + const height = canvas.size[1]; + for (0..height) |y| { + const row = fb[y * width .. (y + 1) * width]; + for (row, 0..width) |*pixel, x| { + pixel.* = .{ + @truncate(x), + @truncate(y), + 0x00, + 0xFF, + }; + } + } + } +}; + +fn wmbaseHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.xdg.WmBase.Event, header, body); + switch (event) { + .ping => |ping| { + try app.xdg_wmbase.pong(ping.serial); + }, + } +} + +fn toplevelHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body); + switch (event) { + .configure => |conf| { + var width = conf.width; + var height = conf.height; + if (conf.width == 0) { + width = 128; + height = 128; + } + if (app.state == .run_mobile) { + app.state.run_mobile.width = @intCast(width); + app.state.run_mobile.height = @intCast(height); + } else { + app.state.run_desktop.width = @intCast(width); + app.state.run_desktop.height = @intCast(height); + } + }, + else => { + std.log.info("toplevel event: {}", .{event}); + }, + } +} + +fn surfaceDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body); + switch (event) { + .configure => |conf| { + try app.xdg_surface.ack_configure(conf.serial); + const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool; + const width = if (app.state == .run_mobile) app.state.run_mobile.width else app.state.run_desktop.width; + const height = if (app.state == .run_mobile) app.state.run_mobile.height else app.state.run_desktop.height; + + // render + const canvas = try pool.getFramebuffer(.{ width, height }); + canvas.renderGradient(); + try canvas.attach(app.surface); + try app.callbacks.put(canvas.buffer.id, bufferHandler); + }, + } +} + +fn pointerDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.core.Pointer.Event, header, body); + switch (event) { + .enter => |enter| { + app.state.run_desktop.mouse.x = @floatFromInt(enter.surface_x); + app.state.run_desktop.mouse.y = @floatFromInt(enter.surface_y); + }, + .leave => |_| {}, + .motion => |motion| { + app.state.run_desktop.mouse.x = @floatFromInt(motion.surface_x); + app.state.run_desktop.mouse.y = @floatFromInt(motion.surface_y); + }, + else => { + std.log.info("pointer event: {}", .{event}); + }, + // .button => |button| { _ = button; }, + // .axis => |axis| { _ = axis;}, + // .frame => {}, + // .axis_source => |axis_source| { _ = axis_source; }, + // .axis_stop => |axis_stop| { _ = axis_stop; }, + // .axis_discrete => |axis_discrete| { _ = axis_discrete; }, + // .axis_value120 => |axis_value120| { _ = axis_value120; }, + // .axis_relative_direction => |axis_relative_direction| {_ = axis_relative_direction; }, + } +} + +fn keyboardDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.core.Keyboard.Event, header, body); + _ = app; + switch (event) { + else => { + std.log.info("keyboard event: {}", .{event}); + }, + // .button => |button| { _ = button; }, + // .axis => |axis| { _ = axis;}, + // .frame => {}, + // .axis_source => |axis_source| { _ = axis_source; }, + // .axis_stop => |axis_stop| { _ = axis_stop; }, + // .axis_discrete => |axis_discrete| { _ = axis_discrete; }, + // .axis_value120 => |axis_value120| { _ = axis_value120; }, + // .axis_relative_direction => |axis_relative_direction| {_ = axis_relative_direction; }, + } +} + +fn bufferHandler(app: *App, header: wayland.Header, body: []const u32) !void { + const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body); + std.debug.assert(event == .release); + const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool; + try pool.freeFramebuffer(header.object_id); +} diff --git a/src/root.zig b/src/root.zig index 79b7bf5..fa33084 100644 --- a/src/root.zig +++ b/src/root.zig @@ -557,8 +557,10 @@ pub fn Context(comptime T: []const type) type { const Item = struct { version: u32, index: u32 }; const Pair = struct { []const u8, Item }; comptime var kvs_list: []const Pair = &[_]Pair{}; + comptime var name_list: []const []const u8 = &[_][]const u8{}; inline for (T, 0..) |t, i| { kvs_list = kvs_list ++ &[_]Pair{.{ t.INTERFACE, .{ .version = t.VERSION, .index = i } }}; + name_list = name_list ++ &[_][]const u8{t.INTERFACE}; } const stringToType = std.ComptimeStringMap(Item, kvs_list); @@ -566,6 +568,8 @@ pub fn Context(comptime T: []const type) type { conn: *Conn, global_ids: [T.len]?u32, + const global_names = name_list; + pub fn init(conn: *Conn) !@This() { var ctx = @This(){ .conn = conn, @@ -575,6 +579,14 @@ pub fn Context(comptime T: []const type) type { return ctx; } + pub fn getGlobalObjectName(ctx: *@This(), id: u32) ?[]const u8 { + for (ctx.global_ids, 0..) |gid_opt, i| { + const gid = gid_opt orelse continue; + if (gid == id) return global_names[i]; + } + return null; + } + pub fn getGlobal(ctx: *@This(), comptime G: type) !G { if (G == core.Display) return .{ .id = 1, .conn = ctx.conn }; const g = stringToType.get(G.INTERFACE) orelse return error.NoSuchGlobal; @@ -598,7 +610,6 @@ pub fn Context(comptime T: []const type) type { try ctx.conn.socket.writeAll(std.mem.sliceAsBytes(message)); } - // var ids: [T.len]?u32 = [_]?u32{null} ** T.len; var message_buffer = std.ArrayList(u32).init(ctx.conn.allocator); defer message_buffer.deinit(); while (true) { @@ -636,7 +647,7 @@ pub fn Context(comptime T: []const type) type { } else if (header.object_id == registry_done_id) { break; } else { - std.log.info("{} {x} \"{}\"", .{ + std.log.info("registerGlobals: {} {x} \"{}\"", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)), diff --git a/src/xdg.zig b/src/xdg.zig index f205f5d..a27fe41 100644 --- a/src/xdg.zig +++ b/src/xdg.zig @@ -63,6 +63,14 @@ pub const WmBase = struct { ); return Surface.init(base.conn, new_id); } + + pub fn pong(base: WmBase, serial: u32) !void { + try base.conn.send( + Request, + base.id, + .{ .pong = .{ .serial = serial } }, + ); + } }; pub const Surface = struct {