Initial commit

This commit is contained in:
Erwin Boskma 2024-05-03 17:16:20 +02:00
commit ab1edbf0f4
Signed by: erwin
SSH key fingerprint: SHA256:/Wk1WZdLg+vQHs3in9qq7PsIp8SMzwGSk/RLZ5zPuZk
11 changed files with 752 additions and 0 deletions

3
.envrc Normal file
View file

@ -0,0 +1,3 @@
dotenv_if_exists
use flake

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
### direnv ###
.direnv
### nix ###
/result
### zig ###
# Zig programming language
zig-cache/
zig-out/
build/
build-*/
docgen_tmp/

127
build.zig Normal file
View file

@ -0,0 +1,127 @@
const std = @import("std");
const mem = std.mem;
const path = std.fs.path;
const Build = std.Build;
const Scanner = @import("zig-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 });
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
scanner.addSystemProtocol("unstable/idle-inhibit/idle-inhibit-unstable-v1.xml");
const wlr_protocols_path = blk: {
const pc_output = b.run(&.{ "pkg-config", "--variable=pkgdatadir", "wlr-protocols" });
break :blk mem.trim(u8, pc_output, &std.ascii.whitespace);
};
scanner.addCustomProtocol(b.pathJoin(&.{ wlr_protocols_path, "unstable/wlr-layer-shell-unstable-v1.xml" }));
scanner.addCustomProtocol(b.pathJoin(&.{ wlr_protocols_path, "unstable/wlr-foreign-toplevel-management-unstable-v1.xml" }));
scanner.generate("wl_compositor", 1);
scanner.generate("wl_shm", 1);
scanner.generate("wl_output", 4);
scanner.generate("xdg_wm_base", 6);
scanner.generate("zwp_idle_inhibit_manager_v1", 1);
scanner.generate("zwlr_foreign_toplevel_manager_v1", 3);
scanner.generate("zwlr_layer_shell_v1", 4);
// const lib = b.addStaticLibrary(.{
// .name = "vegetable-hamper",
// // In this case the main source file is merely a path, however, in more
// // complicated build scripts, this could be a generated file.
// .root_source_file = b.path("src/root.zig"),
// .target = target,
// .optimize = optimize,
// });
// // This declares intent for the library to be installed into the standard
// // location when the user invokes the "install" step (the default step when
// // running `zig build`).
// b.installArtifact(lib);
const exe = b.addExecutable(.{
.name = "vegetable-hamper",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Add wayland module
exe.root_module.addImport("wayland", wayland);
exe.linkLibC();
exe.linkSystemLibrary2("wayland-client", .{ .needed = true });
exe.linkSystemLibrary2("wlroots", .{ .needed = true });
scanner.addCSource(exe);
// 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);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
// const lib_unit_tests = b.addTest(.{
// .root_source_file = b.path("src/root.zig"),
// .target = target,
// .optimize = optimize,
// });
// const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
// const exe_unit_tests = b.addTest(.{
// .root_source_file = b.path("src/main.zig"),
// .target = target,
// .optimize = optimize,
// });
// 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_lib_unit_tests.step);
// test_step.dependOn(&run_exe_unit_tests.step);
}

36
build.zig.zon Normal file
View file

@ -0,0 +1,36 @@
.{
.name = "vegetable-hamper",
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.1.0",
// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.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 = .{
.@"zig-wayland" = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/fe04636c866c6289b74c0803e621c9cc1bc1d1a4.tar.gz",
.hash = "12205b33855e3634201e6777a06d9d50ff8f4477b47ef95024009dd3e60df7b269d3",
},
},
.paths = .{
// This makes *all* files, recursively, included in this package. It is generally
// better to explicitly list the files and directories instead, to insure that
// fetching from tarballs, file system paths, and version control all result
// in the same contents hash.
"",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}

196
flake.lock Normal file
View file

@ -0,0 +1,196 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1714478972,
"narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "2849da033884f54822af194400f8dff435ada242",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1714562304,
"narHash": "sha256-Mr3U37Rh6tH0FbaDFu0aZDwk9mPAe7ASaqDOGgLqqLU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bcd44e224fd68ce7d269b4f44d24c2220fd821e7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1711703276,
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1710695816,
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1714058656,
"narHash": "sha256-Qv4RBm4LKuO4fNOfx9wl40W2rBbv5u5m+whxRYUMiaA=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "c6aaf729f34a36c445618580a9f95a48f5e4e03f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

71
flake.nix Normal file
View file

@ -0,0 +1,71 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ ... }@inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" ];
imports = [
inputs.git-hooks.flakeModule
inputs.treefmt-nix.flakeModule
./nix/zls
];
perSystem =
{
pkgs,
lib,
config,
...
}:
{
treefmt = {
projectRootFile = "flake.lock";
programs = {
deadnix.enable = true;
nixfmt = {
enable = true;
package = pkgs.nixfmt-rfc-style;
};
};
};
devShells.default =
with pkgs;
mkShell {
name = "zig-app";
packages = [
zig_0_12
config.packages.zls
wayland
wayland-protocols
wlroots_0_17
wlr-protocols
swayidle
pkg-config
];
};
};
};
}

54
nix/zls/default.nix Normal file
View file

@ -0,0 +1,54 @@
{
# { lib
# , stdenv
# , fetchFromGitHub
# , zig_0_11
# , callPackage
# }:
perSystem =
{ pkgs, lib, ... }:
{
packages.zls =
let
in
pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "zls";
version = "0.12.0";
src = pkgs.fetchFromGitHub {
owner = "zigtools";
repo = "zls";
rev = finalAttrs.version;
fetchSubmodules = true;
hash = "sha256-2iVDPUj9ExgTooDQmCCtZs3wxBe2be9xjzAk9HedPNY=";
};
langref = pkgs.fetchurl {
url = "https://raw.githubusercontent.com/ziglang/zig/a685ab1499d6560c523f0dbce2890dc140671e43/doc/langref.html.in";
hash = "sha256-7lFSfkVLOrn42nYnYyDmBkkRfM903lUUJZ5Sg+eBUpE=";
};
zigBuildFlags = [ "-Dversion_data_path=${finalAttrs.langref}" ];
nativeBuildInputs = [ pkgs.zig_0_12.hook ];
postPatch = ''
ln -s ${pkgs.callPackage ./deps.nix { }} $ZIG_GLOBAL_CACHE_DIR/p
'';
meta = {
description = "Zig LSP implementation + Zig Language Server";
mainProgram = "zls";
changelog = "https://github.com/zigtools/zls/releases/tag/${finalAttrs.version}";
homepage = "https://github.com/zigtools/zls";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [
figsoda
moni
];
platforms = lib.platforms.unix;
};
});
};
}

20
nix/zls/deps.nix Normal file
View file

@ -0,0 +1,20 @@
# generated by zon2nix (https://github.com/figsoda/zon2nix)
{ linkFarm, fetchzip }:
linkFarm "zig-packages" [
{
name = "12201314cffeb40c5e4e3da166217d2c74628c74486414aaf97422bcd2279915b9fd";
path = fetchzip {
url = "https://github.com/ziglibs/known-folders/archive/bf79988adcfce166f848e4b11e718c1966365329.tar.gz";
hash = "sha256-Q7eMdyScqj8qEiAHg1BnGRTsWSQOKWWTc6hUYHNlgGg=";
};
}
{
name = "12200d71e4b7029ea56a429e24260c6c0e85a3069b0d4ba85eace21a0fd75910aa64";
path = fetchzip {
url = "https://github.com/ziglibs/diffz/archive/e10bf15962e45affb3fcd7d9a950977a69c901b3.tar.gz";
hash = "sha256-yVFPVn4jGfcoE2V4xdTqdThYPutshL6U4feDzetWgFw=";
};
}
]

203
src/main.zig Normal file
View file

@ -0,0 +1,203 @@
const std = @import("std");
const mem = std.mem;
const wayland = @import("wayland");
const wl = wayland.client.wl;
const zwp = wayland.client.zwp;
const zwlr = wayland.client.zwlr;
const version = @import("version.zig").getVersion();
const Context = struct {
toplevel_mgr: ?*zwlr.ForeignToplevelManagerV1,
idle_inhibit_mgr: ?*zwp.IdleInhibitManagerV1,
handles: std.AutoHashMap(*zwlr.ForeignToplevelHandleV1, ToplevelFullscreen),
idle_inhibitor: ?*zwp.IdleInhibitorV1,
shm: ?*wl.Shm,
compositor: ?*wl.Compositor,
layer_shell: ?*zwlr.LayerShellV1,
surface: ?*wl.Surface,
};
const ToplevelFullscreen = enum { Fullscreen, NotFullscreen };
pub fn main() anyerror!void {
std.log.info("Vegetable Hamper v{}", .{version});
var context = Context{
.toplevel_mgr = null,
.idle_inhibit_mgr = null,
.handles = std.AutoHashMap(*zwlr.ForeignToplevelHandleV1, ToplevelFullscreen).init(std.heap.page_allocator),
.idle_inhibitor = null,
.shm = null,
.compositor = null,
.layer_shell = null,
.surface = null,
};
defer context.handles.deinit();
const display = try wl.Display.connect(null);
const registry = try display.getRegistry();
registry.setListener(*Context, registry_listener, &context);
if (display.roundtrip() != .SUCCESS) {
print("Initial roundtrip failed", .{});
return error.RoundTripFailed;
}
const compositor = context.compositor orelse return error.NoWlCompositor;
const shm = context.shm orelse return error.NoWlShm;
const layer_shell = context.layer_shell orelse return error.NoLayerShell;
const toplevel_mgr = context.toplevel_mgr orelse return error.NoTopLevelManager;
_ = context.idle_inhibit_mgr orelse return error.NoIdleInhibitManager;
const buffer = blk: {
const width = 1;
const height = 1;
const stride = width * 4;
const size = stride * height;
const fd = try std.posix.memfd_create("vegetable-hamper", 0);
try std.posix.ftruncate(fd, size);
const data = try std.posix.mmap(null, size, std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0);
// @memcpy(data, @embedFile("res/cat.bgra"));
@memset(data, 0);
const pool = try shm.createPool(fd, size);
defer pool.destroy();
break :blk try pool.createBuffer(0, width, height, stride, wl.Shm.Format.argb8888);
};
context.surface = try compositor.createSurface();
defer context.surface.?.destroy();
const wlr_surface = try layer_shell.getLayerSurface(context.surface.?, null, .background, "vegetable-hamper");
defer wlr_surface.destroy();
wlr_surface.setSize(1, 1);
toplevel_mgr.setListener(*Context, toplevel_manager_listener, &context);
wlr_surface.setListener(*wl.Surface, wlr_surface_listener, context.surface.?);
context.surface.?.commit();
if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
context.surface.?.attach(buffer, 0, 0);
context.surface.?.commit();
while (true) {
if (display.dispatch() != .SUCCESS) return error.DispatchFailed;
}
}
fn wlr_surface_listener(wlr_surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, surface: *wl.Surface) void {
switch (event) {
.configure => |configure| {
wlr_surface.ackConfigure(configure.serial);
surface.commit();
},
.closed => {},
}
}
fn registry_listener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *Context) void {
switch (event) {
.global => |global| {
if (mem.orderZ(u8, global.interface, zwlr.ForeignToplevelManagerV1.getInterface().name) == .eq) {
std.log.debug("Found zwlr_foreign_toplevel_manager_v1", .{});
ctx.toplevel_mgr = registry.bind(global.name, zwlr.ForeignToplevelManagerV1, 1) catch return;
} else if (mem.orderZ(u8, global.interface, zwp.IdleInhibitManagerV1.getInterface().name) == .eq) {
std.log.debug("Found zwp_idle_inhibit_manager_v1", .{});
ctx.idle_inhibit_mgr = registry.bind(global.name, zwp.IdleInhibitManagerV1, 1) catch @panic("Error binding idle inhibit manager");
} else if (mem.orderZ(u8, global.interface, wl.Compositor.getInterface().name) == .eq) {
std.log.debug("Found wl_compositor", .{});
ctx.compositor = registry.bind(global.name, wl.Compositor, 1) catch return;
} else if (mem.orderZ(u8, global.interface, wl.Shm.getInterface().name) == .eq) {
std.log.debug("Found wl_shm", .{});
ctx.shm = registry.bind(global.name, wl.Shm, 1) catch return;
} else if (mem.orderZ(u8, global.interface, zwlr.LayerShellV1.getInterface().name) == .eq) {
std.log.debug("Found wlr_layer_shell", .{});
ctx.layer_shell = registry.bind(global.name, zwlr.LayerShellV1, 1) catch return;
} else {
std.log.debug("Unhandled interface: {s}", .{global.interface});
}
},
.global_remove => {},
}
}
fn toplevel_manager_listener(manager: *zwlr.ForeignToplevelManagerV1, event: zwlr.ForeignToplevelManagerV1.Event, ctx: *Context) void {
_ = manager;
var handles = &ctx.handles;
switch (event) {
.toplevel => |toplevel| {
std.log.debug("New toplevel", .{});
handles.put(toplevel.toplevel, .NotFullscreen) catch return;
toplevel.toplevel.setListener(*Context, toplevel_listener, ctx);
},
.finished => {},
}
}
fn toplevel_listener(handle: *zwlr.ForeignToplevelHandleV1, event: zwlr.ForeignToplevelHandleV1.Event, ctx: *Context) void {
var handles = &ctx.handles;
switch (event) {
.state => |stateEvent| {
const states = stateEvent.state.slice(c_int);
std.log.debug("State: {x}", .{states});
for (states) |s| {
const state: zwlr.ForeignToplevelHandleV1.State = @enumFromInt(s);
switch (state) {
.fullscreen => {
std.log.debug("Something went fullscreen", .{});
ctx.handles.put(handle, .Fullscreen) catch return;
if (ctx.idle_inhibitor == null) {
ctx.idle_inhibitor = ctx.idle_inhibit_mgr.?.createInhibitor(ctx.surface.?) catch @panic("Error creating idle inhibitor");
print("Now hampering vegetation", .{});
}
},
else => {
ctx.handles.put(handle, .NotFullscreen) catch return;
var values = ctx.handles.valueIterator();
const someFullscreen = while (values.next()) |fs| {
if (fs.* == .Fullscreen) {
break true;
}
} else false;
if (!someFullscreen and ctx.idle_inhibitor != null) {
ctx.idle_inhibitor.?.destroy();
ctx.idle_inhibitor = null;
print("No more full screen applications, no more hampering", .{});
}
},
}
}
},
.title => {},
.app_id => {},
.output_enter => {},
.output_leave => {},
.done => {},
.closed => {
std.log.debug("Closed", .{});
_ = handles.remove(handle);
},
.parent => {},
}
}
fn print(comptime format: []const u8, args: anytype) void {
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
stdout.print(format ++ "\n", args) catch @panic("Could not write to stdout");
bw.flush() catch @panic("Could not flush stdout"); // don't forget to flush!
}

10
src/root.zig Normal file
View file

@ -0,0 +1,10 @@
const std = @import("std");
const testing = std.testing;
export fn add(a: i32, b: i32) i32 {
return a + b;
}
test "basic add functionality" {
try testing.expect(add(3, 7) == 10);
}

18
src/version.zig Normal file
View file

@ -0,0 +1,18 @@
const std = @import("std");
pub fn getVersion() Version {
return Version{};
}
const Version = struct {
comptime major: u8 = 0,
comptime minor: u8 = 1,
comptime patch: u8 = 0,
const Self = @This();
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
try writer.print("{}.{}.{}", .{ self.major, self.minor, self.patch });
}
};