timewall 2.0.2

All-in-one tool for Apple dynamic HEIF wallpapers on GNU/Linux
self: {
  config,
  lib,
  pkgs,
  ...
}: let
  inherit (pkgs.stdenv.hostPlatform) system;
  cfg = config.services.timewall;
  configFormat = pkgs.formats.toml {};
in {
  options = {
    services.timewall = {
      enable = lib.mkEnableOption "timewall";

      package = lib.mkPackageOption self.packages.${system} "timewall" {
        default = "default";
        pkgsText = "timewall.packages.\${pkgs.system}";
      };

      systemdTarget = lib.mkOption {
        type = lib.types.str;
        default = "graphical-session.target";
        description = "Systemd target to bind to.";
      };

      wallpaperPath = lib.mkOption {
        type = with lib.types; nullOr path;
        default = null;
        description = ''
          Path to the HEIF/HEIC dynamic wallpaper file to set.
          If not set, you need to manually set the wallpaper by running `timewall set`!
          Otherwise the timewall daemon will fail.
        '';
      };

      config = {
        geoclue = {
          enable = lib.mkOption {
            type = lib.types.bool;
            default = true;
            description = "Enable GeoClue 2 for automatic location detection.";
          };
          cache_fallback = lib.mkOption {
            type = lib.types.bool;
            default = true;
            description = ''
              Whether to fallback to the last known location if GeoClue 2 fails to return
              a location.
            '';
          };
          prefer = lib.mkOption {
            type = lib.types.bool;
            default = false;
            description = "Prefer GeoClue 2 over manual location configuration.";
          };
          timeout = lib.mkOption {
            type = lib.types.int;
            default = 1000;
            description = ''
              Time in milliseconds to wait for GeoClue 2 to return a location.
              After this time `timewall` will fallback to manual location configuration
              or fail, depending on the `prefer` option.
            '';
          };
        };

        location = {
          lat = lib.mkOption {
            type = with lib.types; nullOr (either float int);
            default = null;
            description = "Your geographical latitude.";
          };
          lon = lib.mkOption {
            type = with lib.types; nullOr (either float int);
            default = null;
            description = "You geographical longitude.";
          };
        };

        setter = {
          command = lib.mkOption {
            type = with lib.types; nullOr (listOf str);
            default = null;
            description = ''
              Command to set the wallpaper. Use "%f" as a placeholder for the file path.
              The command is NOT passed through a shell.
            '';
            example = ["sww" "img" "%f"];
          };
          overlap = lib.mkOption {
            type = lib.types.int;
            default = 0;
            description = ''
              Time overlap in milliseconds between spawning a new setter command and
              terminating the previous one. This is useful for long running setters that
              don't terminate immediately after setting the wallpaper.
            '';
          };
          quiet = lib.mkOption {
            type = lib.types.bool;
            default = true;
            description = "Whether to suppress the setter command output.";
          };
        };

        daemon = {
          update_interval_seconds = lib.mkOption {
            type = lib.types.int;
            default = 600;
            description = "Interval between wallpaper updates in seconds.";
          };
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = (cfg.config.location.lat != null) == (cfg.config.location.lon != null);
        message = "Both `latitude and `longitude` must be set for timewall";
      }
    ];

    home.packages = [cfg.package];

    xdg.configFile."timewall/config.toml".source = configFormat.generate "config.toml" (
      {
        inherit (cfg.config) daemon;
        inherit (cfg.config) geoclue;
      }
      // lib.optionalAttrs (cfg.config.location.lat != null && cfg.config.location.lon != null) {
        inherit (cfg.config) location;
      }
      // lib.optionalAttrs (cfg.config.setter.command != null) {
        inherit (cfg.config) setter;
      }
    );

    systemd.user.services.timewall = {
      Unit = {
        Description = "Dynamic wallpapers daemon";
        PartOf = [cfg.systemdTarget];
        After = [cfg.systemdTarget];
        X-Restart-Triggers =
          lib.mkIf (cfg.config != {})
          ["${config.xdg.configFile."timewall/config.toml".source}"];
      };
      Service = {
        ExecStart = builtins.concatStringsSep " " [
          "${lib.getExe cfg.package} set --daemon"
          (
            if cfg.wallpaperPath != null
            then builtins.toString cfg.wallpaperPath
            else ""
          )
        ];
        Restart = "on-failure";
      };
      Install.WantedBy = [cfg.systemdTarget];
    };
  };
}