From f77d372b778613a91a8d2ef16bf10a19220ad177 Mon Sep 17 00:00:00 2001 From: faukah Date: Sun, 27 Jul 2025 18:28:10 +0200 Subject: [PATCH] initial commit Signed-off-by: faukah Change-Id: I6a6a69645add91c6178de9a2b56e175e3828bfca --- .envrc | 1 + .gitignore | 4 ++ build.zig | 93 ++++++++++++++++++++++++++++ build.zig.zon | 56 +++++++++++++++++ flake.lock | 27 ++++++++ flake.nix | 34 +++++++++++ src/main.zig | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/root.zig | 13 ++++ 8 files changed, 394 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.zig create mode 100644 src/root.zig diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e9f4a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.zig-cache +zig-out +.direnv +target diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..e49ac35 --- /dev/null +++ b/build.zig @@ -0,0 +1,93 @@ +const std = @import("std"); + +const Scanner = @import("wayland").Scanner; + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const scanner = Scanner.create(b, .{}); + + const wayland = b.createModule(.{ .root_source_file = scanner.result, .target = target, .optimize = optimize }); + + const wlr_protocols = b.dependency("wlr-protocols", .{}); + + scanner.addCustomProtocol(wlr_protocols.path("unstable/wlr-layer-shell-unstable-v1.xml")); + scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); + + scanner.generate("wl_shm", 1); + scanner.generate("xdg_wm_base", 2); + scanner.generate("wl_output", 4); + scanner.generate("zwlr_layer_shell_v1", 1); + scanner.generate("wl_compositor", 5); + + // We will also create a module for our other entry point, 'main.zig'. + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + exe_mod.addImport("wayland", wayland); + + // This creates another `std.Build.Step.Compile`, but this one builds an executable + // rather than a static library. + const exe = b.addExecutable(.{ + .name = "zayneko", + .root_module = exe_mod, + }); + + exe.linkLibC(); + exe.linkSystemLibrary("wayland-client"); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..8143475 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,56 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .zayneko, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // Together with name, this represents a globally unique package + // identifier. This field is generated by the Zig toolchain when the + // package is first created, and then *never changes*. This allows + // unambiguous detection of one package being an updated version of + // another. + // + // When forking a Zig project, this id should be regenerated (delete the + // field and run `zig build`) if the upstream project is still maintained. + // Otherwise, the fork is *hostile*, attempting to take control over the + // original project's identity. Thus it is recommended to leave the comment + // on the following line intact, so that it shows up in code reviews that + // modify the field. + .fingerprint = 0x2cff4904a359ccff, // Changing this has security and trust implications. + + // Tracks the earliest Zig version that the package considers to be a + // supported use case. + .minimum_zig_version = "0.14.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + .wayland = .{ + .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.3.0.tar.gz", + .hash = "wayland-0.3.0-lQa1kjPIAQDmhGYpY-zxiRzQJFHQ2VqhJkQLbKKdt5wl", + }, + .@"wlr-protocols" = .{ + .url = "git+https://gitlab.freedesktop.org/wlroots/wlr-protocols.git#2ec67ebd26b73bada12f3fa6afdd51563b656722", + .hash = "N-V-__8AAMm4AQBQdtoOaA3SHeMraj1_jhjp6KQJy4_dbQnz", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7c684b0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1747327360, + "narHash": "sha256-LSmTbiq/nqZR9B2t4MRnWG7cb0KVNU70dB7RT4+wYK4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "e06158e58f3adee28b139e9c2bcfcc41f8625b46", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c9ba8a5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,34 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { + self, + nixpkgs, + } @ inputs: let + inherit (inputs.nixpkgs) lib; + eachSystem = lib.genAttrs (import inputs.systems); + pkgsFor = inputs.nixpkgs.legacyPackages; + in { + devShells = + lib.mapAttrs (system: pkgs: { + default = pkgs.mkShell { + packages = with pkgs; [ + pkg-config + wayland-scanner + zig + wayland + wayland-protocols + ]; + + shellHook = '' + export LD_LIBRARY_PATH=${pkgs.wayland}/lib:$LD_LIBRARY_PATH + ''; + }; + }) + pkgsFor; + }; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..1b8728f --- /dev/null +++ b/src/main.zig @@ -0,0 +1,166 @@ +const std = @import("std"); +const mem = std.mem; +const posix = std.posix; + +const wayland = @import("wayland"); +const wl = wayland.client.wl; +const zwlr = wayland.client.zwlr; +const layer_shell = zwlr.common.zwlr.layer_shell_v1; + +const Context = struct { + shm: ?*wl.Shm = null, + + shell: *zwlr.LayerShellV1 = undefined, + surface: *wl.Surface = undefined, + + layer_surface: *zwlr.LayerSurfaceV1 = undefined, + buffer: ?*wl.Buffer = null, + + compositor: *wl.Compositor = undefined, + blk: []u32 = undefined, + + // width and height of our context + width: i32 = 0, + height: i32 = 0, +}; + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const display = try wl.Display.connect(null); + + defer display.disconnect(); + + const registry = try display.getRegistry(); + + defer registry.destroy(); + + var context = Context{}; + registry.setListener(*Context, registryListener, &context); + if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; + + context.surface = try context.compositor.createSurface(); + defer context.surface.destroy(); + + context.layer_surface = try context.shell.getLayerSurface(context.surface, null, .top, "zayneko"); + defer context.layer_surface.destroy(); + + context.layer_surface.setExclusiveZone(0); + context.layer_surface.setAnchor(.{ + .top = false, + .bottom = true, + .left = true, + .right = true, + }); + + context.layer_surface.setSize(0, 30); + context.layer_surface.setListener(*Context, layerSurfaceListener, &context); + + context.surface.commit(); + if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; + + std.debug.print("{}\n", .{context.blk.len}); + std.debug.print("Width: {}\n", .{context.width}); + std.debug.print("Height: {}\n", .{context.height}); + + context.surface.attach(context.buffer, 0, 0); + context.surface.commit(); + if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; + + while (true) { + const segment: usize = + @intCast(@divFloor(context.width, 10)); + for (0..segment) |i| { + std.debug.print("{}\n", .{i}); + const casted: i32 = @intCast(i); + + moveBuffer(casted, &context); + _ = display.flush(); + if (display.dispatchPending() != .SUCCESS) return error.DispatchFailed; + std.time.sleep(5000000); + } + for (0..segment) |i| { + // const casted: i32 = @intCast(i); + std.debug.print("smaller {}\n", .{(segment - i)}); + moveBuffer(@intCast(segment - i), &context); + _ = display.flush(); + if (display.dispatchPending() != .SUCCESS) return error.DispatchFailed; + std.time.sleep(5000000); + } + } + + // std.time.sleep(4000000000); +} + +fn registryListener( + registry: *wl.Registry, + event: wl.Registry.Event, + context: *Context, +) void { + switch (event) { + .global => |global| { + if (mem.orderZ(u8, global.interface, wl.Shm.interface.name) == .eq) { + context.shm = registry.bind(global.name, wl.Shm, 1) catch return; + } else if (mem.orderZ(u8, global.interface, zwlr.LayerShellV1.interface.name) == .eq) { + context.shell = registry.bind(global.name, zwlr.LayerShellV1, 4) catch return; + } else if (mem.orderZ(u8, global.interface, wl.Compositor.interface.name) == .eq) { + context.compositor = registry.bind(global.name, wl.Compositor, 5) catch return; + } + }, + .global_remove => {}, + } +} + +fn moveBuffer(offset: i32, context: *Context) void { + @memset(context.blk, 0); + + for (context.blk, 0..context.blk.len) |*a, i| { + const context_width: usize = @intCast(context.width); + if (i % context_width > offset * 10 and i % context_width < offset * 10 + @divFloor(context.width, 10)) + a.* = 0xFFFFFFFF; + } + context.surface.attach(context.buffer, 0, 0); + context.surface.damageBuffer(offset * 10 - 10, 0, @divFloor(context.width, 10) * 2, context.height); + context.surface.commit(); +} + +fn layerSurfaceListener( + surface: *zwlr.LayerSurfaceV1, + event: zwlr.LayerSurfaceV1.Event, + context: *Context, +) void { + switch (event) { + .configure => |configure| { + surface.ackConfigure(configure.serial); + + context.buffer = blk: { + const width: i32 = @intCast((configure.width)); + const height: i32 = @intCast((configure.height)); + + context.width = width; + context.height = height; + + const stride: i32 = width * 4; + const size: i32 = stride * height; + const sizeu = configure.width * configure.height * 4; + + const fd = posix.memfd_create("zayneko", 0) catch return; + posix.ftruncate(fd, sizeu) catch return; + + const tmp align(@alignOf(u32)) = posix.mmap(null, sizeu, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0) catch return; + + context.blk = mem.bytesAsSlice(u32, tmp); + + const pool = context.shm.?.createPool(fd, size) catch return; + + defer pool.destroy(); + + break :blk pool.createBuffer(0, width, height, stride, .argb8888) catch null; + }; + context.surface.attach(context.buffer, 0, 0); + context.surface.commit(); + }, + .closed => {}, + } +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..27d2be8 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,13 @@ +//! By convention, root.zig is the root source file when making a library. If +//! you are making an executable, the convention is to delete this file and +//! start with main.zig instead. +const std = @import("std"); +const testing = std.testing; + +pub export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +}