diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | build.zig | 44 | ||||
| -rw-r--r-- | build.zig.zon | 18 | ||||
| -rw-r--r-- | src/main.zig | 200 | 
4 files changed, 264 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..941c29f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.zig-cache +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..5e885fc --- /dev/null +++ b/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +const Scanner = @import("zig-wayland").Scanner; + +pub fn build(b: *std.Build) void { +    const target = b.standardTargetOptions(.{}); + +    const optimize = b.standardOptimizeOption(.{}); + +    const scanner = Scanner.create(b, .{}); +    scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); +    scanner.addSystemProtocol("staging/fractional-scale/fractional-scale-v1.xml"); +    scanner.generate("wl_compositor", 6); +    scanner.generate("wl_shm", 1); +    scanner.generate("wl_output", 4); +    scanner.generate("xdg_wm_base", 2); +    scanner.generate("wp_fractional_scale_manager_v1", 1); + +    const wayland = b.createModule(.{ .root_source_file = scanner.result }); + +    const exe = b.addExecutable(.{ +        .name = "zigway", +        .root_source_file = b.path("src/main.zig"), +        .target = target, +        .optimize = optimize, +    }); + +    exe.root_module.addImport("wayland", wayland); +    exe.linkLibC(); +    exe.linkSystemLibrary("wayland-client"); + +    scanner.addCSource(exe); + +    b.installArtifact(exe); + +    const run_cmd = b.addRunArtifact(exe); +    run_cmd.step.dependOn(b.getInstallStep()); +    if (b.args) |args| { +        run_cmd.addArgs(args); +    } + +    const run_step = b.step("run", "Run the app"); +    run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..925fe00 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,18 @@ +.{ +    .name = "hello-wl", +    .version = "0.0.0", +    //.minimum_zig_version = "0.11.0", +    .dependencies = .{ +        .@"zig-wayland" = .{ +            .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.2.0.tar.gz", +            .hash = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242", +        }, +    }, +    .paths = .{ +        "build.zig", +        "build.zig.zon", +        "src", +        //"LICENSE", +        //"README.md", +    }, +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..5f83b8e --- /dev/null +++ b/src/main.zig @@ -0,0 +1,200 @@ +const std = @import("std"); +const mem = std.mem; +const posix = std.posix; + +const wayland = @import("wayland"); +const wl = wayland.client.wl; +const xdg = wayland.client.xdg; + +const RegistryContext = struct { +    shm: ?*wl.Shm, +    compositor: ?*wl.Compositor, +    wm_base: ?*xdg.WmBase, +}; + +const Window = struct { +    shm: *wl.Shm, +    wl_surface: *wl.Surface, +    width: i32, +    height: i32, +    offset: f32, +    last_frame: u32, +    run: bool, + +    pub fn drawFrame(state: *Window) !*wl.Buffer { +        const stride = state.width * 4; +        const size = stride * state.height; + +        const fd = try posix.memfd_create("zigway", 0); +        defer posix.close(fd); +        try posix.ftruncate(fd, @intCast(size)); +        var data = try posix.mmap( +            null, +            @intCast(size), +            posix.PROT.READ | posix.PROT.WRITE, +            .{ .TYPE = .SHARED }, +            fd, +            0, +        ); +        defer posix.munmap(data); + +        const pool = try state.shm.createPool(fd, size); +        defer pool.destroy(); +        const buffer = try pool.createBuffer(0, state.width, state.height, stride, .argb8888); + +        const canvas: *[]u32 = @ptrCast(&data); +        const box_side = 12; +        var offset: u32 = @intFromFloat(state.offset); +        offset = @rem(offset, box_side); +        const height: u32 = @intCast(state.height); +        const width: u32 = @intCast(state.width); +        for (0..height) |y| { +            for (0..width) |x| { +                if ((x + offset + (y + offset) / box_side * box_side) % (box_side * 2) < box_side) { +                    canvas.*[y * width + x] = 0xFF0E0E0E; +                } else { +                    canvas.*[y * width + x] = 0xFF2F2F2F; +                } +            } +        } + +        buffer.setListener(?*void, bufferListener, null); +        return buffer; +    } +}; + +pub fn main() !void { +    const display = try wl.Display.connect(null); +    defer display.disconnect(); +    const registry = try display.getRegistry(); + +    var ctx = RegistryContext{ +        .shm = null, +        .compositor = null, +        .wm_base = null, +    }; + +    registry.setListener(*RegistryContext, registryListener, &ctx); +    if (display.roundtrip() != .SUCCESS) return error.RoundTripFailed; + +    const compositor = ctx.compositor orelse return error.NoWlCompositor; +    const wm_base = ctx.wm_base orelse return error.NoXdgWmBase; + +    var state = Window{ +        .shm = ctx.shm orelse return error.NoWlShm, +        .wl_surface = try compositor.createSurface(), +        .width = 480, +        .height = 360, +        .offset = 0.0, +        .last_frame = 0, +        .run = true, +    }; +    defer state.wl_surface.destroy(); + +    const xdg_surface = try wm_base.getXdgSurface(state.wl_surface); +    defer xdg_surface.destroy(); +    const xdg_toplevel = try xdg_surface.getToplevel(); +    defer xdg_toplevel.destroy(); + +    xdg_surface.setListener(*Window, xdgSurfaceListener, &state); +    xdg_toplevel.setListener(*Window, xdgToplevelListener, &state); +    wm_base.setListener(?*void, xdgWmBaseListener, null); + +    xdg_toplevel.setTitle("Hello, wayland!"); + +    state.wl_surface.commit(); + +    const wl_cb = try state.wl_surface.frame(); +    wl_cb.setListener(*Window, frameListener, &state); + +    while (state.run) { +        if (display.dispatch() != .SUCCESS) return error.DispatchFailed; +    } +} + +fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *RegistryContext) void { +    switch (event) { +        .global => |global| { +            std.debug.print("inteface: {s}, version {}, name {}\n", .{ +                global.interface, +                global.version, +                global.name, +            }); +            if (mem.orderZ(u8, global.interface, wl.Compositor.getInterface().name) == .eq) { +                ctx.compositor = registry.bind(global.name, wl.Compositor, 6) catch return; +            } else if (mem.orderZ(u8, global.interface, wl.Shm.getInterface().name) == .eq) { +                ctx.shm = registry.bind(global.name, wl.Shm, 1) catch return; +            } else if (mem.orderZ(u8, global.interface, xdg.WmBase.getInterface().name) == .eq) { +                ctx.wm_base = registry.bind(global.name, xdg.WmBase, 2) catch return; +            } +        }, +        .global_remove => {}, +    } +} + +fn xdgSurfaceListener(xdg_surface: *xdg.Surface, event: xdg.Surface.Event, state: *Window) void { +    switch (event) { +        .configure => |configure| { +            xdg_surface.ackConfigure(configure.serial); +            brk: { +                const buffer = state.drawFrame() catch |err| { +                    std.debug.print("error drawing frame {}\n", .{err}); +                    break :brk; +                }; +                state.wl_surface.attach(buffer, 0, 0); +            } +            state.wl_surface.commit(); +        }, +    } +} + +fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, state: *Window) void { +    switch (event) { +        .configure => |configure| { +            state.height = if (configure.height != 0) configure.height else state.height; +            state.width = if (configure.width != 0) configure.width else state.width; +        }, +        .close => state.run = false, +    } +} +fn xdgWmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: ?*void) void { +    switch (event) { +        .ping => |ping| { +            wm_base.pong(ping.serial); +        }, +    } +} + +fn bufferListener(buffer: *wl.Buffer, event: wl.Buffer.Event, _: ?*void) void { +    switch (event) { +        .release => buffer.destroy(), +    } +} + +fn frameListener(wl_cb: *wl.Callback, event: wl.Callback.Event, state: *Window) void { +    switch (event) { +        .done => |done| { +            wl_cb.destroy(); +            const new_cb = state.wl_surface.frame() catch |err| { +                std.debug.print("unable to get frame {}\n", .{err}); +                state.run = false; +                return; +            }; +            new_cb.setListener(*Window, frameListener, state); + +            if (state.last_frame != 0) { +                const elapsed: f32 = @floatFromInt(done.callback_data -% state.last_frame); +                state.offset += elapsed / 1000.0 * 48.0; +            } +            defer state.last_frame = done.callback_data; + +            const buffer = state.drawFrame() catch |err| { +                std.debug.print("error drawing frame {}\n", .{err}); +                return; +            }; +            state.wl_surface.attach(buffer, 0, 0); +            state.wl_surface.damageBuffer(0, 0, std.math.maxInt(i32), std.math.maxInt(i32)); +            state.wl_surface.commit(); +        }, +    } +} | 
