1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
{ depot, pkgs, ... }:

# The niri lock screen: swaylock with a renderer that draws its background,
# crystal-shader running crystal.frag. See crystal-lock.1 for what it does
# and why.
#
# swaylock is nixpkgs' own with one patch (rather than a fork), so upstream
# fixes keep coming and a nixpkgs bump that touches the patched code fails
# to build instead of silently mixing versions. crystal-swaylock.patch is
# the security-relevant part: the renderer (an absolute path after `--`,
# started without a shell) never talks to the compositor, it hands swaylock
# finished frames as dmabufs, which swaylock checks and shows; plus the
# channel telling it what happens, --unlock-delay with its sleep watcher,
# and the PAM service as a build flag. See its header and crystal-lock.1,
# SECURITY CONSIDERATIONS.
#
# Three Go programs, one module (standard library only):
#
# - crystal-lock (main.go, cache.go): picks the wallpaper and starts
#   swaylock by absolute path; the paths are linked in below. cache.go keeps
#   the wallpapers scaled down to the screen, so they decode fast. buildGo,
#   static.
# - crystal-shader (crystal-shader/): the renderer. Its GPU side (gl.c:
#   headless EGL on GBM) needs cgo, which buildGo cannot do (buildGo.7,
#   CAVEATS), hence buildGoModule.
# - shader/: the renderer's state (messages to uniforms), pure Go.
#
# Tests (not run by the builds; crystal-shader has none and needs the
# GL headers):
#
#     go test ./users/Profpatsch/crystal-lock/ ./users/Profpatsch/crystal-lock/shader/
let
  swaylock = pkgs.swaylock.overrideAttrs (old: {
    patches = (old.patches or [ ]) ++ [ ./crystal-swaylock.patch ];
    # swaylock-sleep-watcher, for --unlock-delay
    buildInputs = old.buildInputs ++ [ pkgs.systemd ];
    mesonFlags = (old.mesonFlags or [ ]) ++ [ "-Dlogind=enabled" ];
    # authenticate like the plain lock screen, against the existing
    # security.pam.services.swaylock (niri.nix); also upstream's default,
    # pinned here so that the fallback and this one cannot drift apart
    env = (old.env or { }) // {
      NIX_CFLAGS_COMPILE = toString (old.env.NIX_CFLAGS_COMPILE or "")
        + ''-DSWAYLOCK_PAM_SERVICE="swaylock"'';
    };
  });

  crystal-shader = pkgs.buildGoModule {
    pname = "crystal-shader";
    version = "0";
    # only what the build reads, so stray files (./tmp, binaries) never
    # reach the store
    src = depot.users.Profpatsch.exactSource ./. [
      ./go.mod
      ./shader/state.go
      ./crystal-shader/main.go
      ./crystal-shader/gl.c
      ./crystal-shader/gl.h
    ];
    vendorHash = null;
    subPackages = [ "crystal-shader" ];
    # the root go.work lists modules this one does not see
    env.GOWORK = "off";
    nativeBuildInputs = [ pkgs.pkg-config ];
    buildInputs = [ pkgs.libgbm pkgs.libglvnd ];
    meta.mainProgram = "crystal-shader";
  };

  shader = pkgs.writeText "crystal.frag" (builtins.readFile ./crystal.frag);

  bin = depot.nix.buildGo.program {
    name = "crystal-lock";
    srcs = [ ./main.go ./cache.go ];
    x_defs = {
      "main.swaylockBin" = "${swaylock}/bin/swaylock";
      "main.crystalShader" = "${crystal-shader}/bin/crystal-shader";
      "main.shader" = "${shader}";
      "main.swaylock" = "${pkgs.swaylock}/bin/swaylock";
      "main.systemctl" = "${pkgs.systemd}/bin/systemctl";
    };
  };
in
pkgs.runCommand "crystal-lock"
{
  nativeBuildInputs = [ pkgs.installShellFiles ];
  passthru = { inherit swaylock crystal-shader shader bin; };
  meta.mainProgram = "crystal-lock";
} ''
  mkdir --parents $out/bin
  ln -s ${bin}/bin/crystal-lock $out/bin/crystal-lock
  installManPage ${./crystal-lock.1}
''