Unverified Commit d2495395 authored by Tomo's avatar Tomo Committed by GitHub
Browse files

wivrn: init at 0.19 (#316975)

parents 18885773 6e9e75f7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -201,6 +201,8 @@ The pre-existing `services.ankisyncd` has been marked deprecated and will be dro

- [watchdogd](https://troglobit.com/projects/watchdogd/), a system and process supervisor using watchdog timers. Available as [services.watchdogd](#opt-services.watchdogd.enable).

- [WiVRn](https://github.com/Meumeu/WiVRn), an OpenXR streaming application. Available as [services.wivrn](#opt-services.wivrn.enable).

- [Workout-tracker](https://github.com/jovandeginste/workout-tracker), a workout tracking web application for personal use.

- [wyoming-satellite](https://github.com/rhasspy/wyoming-satellite), a voice assistant satellite for Home Assistant using the Wyoming protocol. Available as [services.wyoming.satellite](#opt-services.wyoming.satellite.enable).
+1 −0
Original line number Diff line number Diff line
@@ -1380,6 +1380,7 @@
  ./services/video/photonvision.nix
  ./services/video/mediamtx.nix
  ./services/video/v4l2-relayd.nix
  ./services/video/wivrn.nix
  ./services/wayland/cage.nix
  ./services/wayland/hypridle.nix
  ./services/web-apps/akkoma.nix
+226 −0
Original line number Diff line number Diff line
{
  config,
  pkgs,
  lib,
  ...
}:
let
  inherit (lib)
    mkIf
    mkEnableOption
    mkPackageOption
    mkOption
    optionalString
    optionalAttrs
    isDerivation
    recursiveUpdate
    getExe
    literalExpression
    types
    maintainers
    ;
  cfg = config.services.wivrn;
  configFormat = pkgs.formats.json { };

  # For the application option to work with systemd PATH, we find the store binary path of
  # the package, concat all of the following strings, and then update the application attribute.
  # Application can either be a package or a list that has a package as the first element.
  applicationExists = builtins.hasAttr "application" cfg.config.json;
  applicationListNotEmpty = (
    if builtins.isList cfg.config.json.application then
      (builtins.length cfg.config.json.application) != 0
    else
      true
  );
  applicationCheck = applicationExists && applicationListNotEmpty;

  applicationBinary = (
    if builtins.isList cfg.config.json.application then
      builtins.head cfg.config.json.application
    else
      cfg.config.json.application
  );
  applicationStrings = builtins.tail cfg.config.json.application;

  applicationPath = mkIf applicationCheck applicationBinary;

  applicationConcat = (
    if builtins.isList cfg.config.json.application then
      builtins.concatStringsSep " " ([ (getExe applicationBinary) ] ++ applicationStrings)
    else
      (getExe applicationBinary)
  );
  applicationUpdate = recursiveUpdate cfg.config.json (
    optionalAttrs applicationCheck { application = applicationConcat; }
  );
  configFile = configFormat.generate "config.json" applicationUpdate;
in
{
  options = {
    services.wivrn = {
      enable = mkEnableOption "WiVRn, an OpenXR streaming application";

      package = mkPackageOption pkgs "wivrn" { };

      openFirewall = mkEnableOption "the default ports in the firewall for the WiVRn server";

      defaultRuntime = mkEnableOption ''
        WiVRn Monado as the default OpenXR runtime on the system.
        The config can be found at `/etc/xdg/openxr/1/active_runtime.json`.

        Note that applications can bypass this option by setting an active
        runtime in a writable XDG_CONFIG_DIRS location like `~/.config`
      '';

      autoStart = mkEnableOption "starting the service by default";

      monadoEnvironment = mkOption {
        type = types.attrs;
        description = "Environment variables to be passed to the Monado environment.";
        default = {
          XRT_COMPOSITOR_LOG = "debug";
          XRT_PRINT_OPTIONS = "on";
          IPC_EXIT_ON_DISCONNECT = "off";
        };
      };

      extraPackages = mkOption {
        type = types.listOf types.package;
        description = "Packages to add to the wivrn-application service $PATH.";
        default = [ ];
        example = literalExpression "[ pkgs.bash pkgs.procps ]";
      };

      config = {
        enable = mkEnableOption "configuration for WiVRn";
        json = mkOption {
          type = configFormat.type;
          description = ''
            Configuration for WiVRn. The attributes are serialized to JSON in config.json.

            Note that the application option must be either a package or a
            list with package as the first element.

            See https://github.com/Meumeu/WiVRn/blob/master/docs/configuration.md
          '';
          default = { };
          example = literalExpression ''
            {
              scale = 0.8;
              bitrate = 100000000;
              encoders = [
                {
                  encoder = "nvenc";
                  codec = "h264";
                  width = 1.0;
                  height = 1.0;
                  offset_x = 0.0;
                  offset_y = 0.0;
                }
              ];
              application = [ pkgs.wlx-overlay-s ];
              tcp_only = true;
            }
          '';
        };
      };
    };
  };

  config = mkIf cfg.enable {
    assertions = [
      {
        assertion = !applicationCheck || isDerivation applicationBinary;
        message = "The application in WiVRn configuration is not a package. Please ensure that the application is a package or that a package is the first element in the list.";
      }
    ];

    systemd.user = {
      services = {
        # The WiVRn server runs in a hardened service and starts the applications in a different service
        wivrn = {
          description = "WiVRn XR runtime service";
          environment = {
            # Default options
            # https://gitlab.freedesktop.org/monado/monado/-/blob/598080453545c6bf313829e5780ffb7dde9b79dc/src/xrt/targets/service/monado.in.service#L12
            XRT_COMPOSITOR_LOG = "debug";
            XRT_PRINT_OPTIONS = "on";
            IPC_EXIT_ON_DISCONNECT = "off";
          } // cfg.monadoEnvironment;
          serviceConfig = {
            ExecStart = (
              (getExe cfg.package) + " --systemd" + optionalString cfg.config.enable " -f ${configFile}"
            );
            # Hardening options
            CapabilityBoundingSet = [ "CAP_SYS_NICE" ];
            AmbientCapabilities = [ "CAP_SYS_NICE" ];
            LockPersonality = true;
            NoNewPrivileges = true;
            PrivateTmp = true;
            ProtectClock = true;
            ProtectControlGroups = true;
            ProtectKernelLogs = true;
            ProtectKernelModules = true;
            ProtectKernelTunables = true;
            ProtectProc = "invisible";
            ProtectSystem = "strict";
            RemoveIPC = true;
            RestrictNamespaces = true;
            RestrictSUIDSGID = true;
          };
          wantedBy = mkIf cfg.autoStart [ "default.target" ];
          restartTriggers = [
            cfg.package
            configFile
          ];
        };
        wivrn-application = mkIf applicationCheck {
          description = "WiVRn application service";
          requires = [ "wivrn.service" ];
          serviceConfig = {
            ExecStart = (
              (getExe cfg.package) + " --application" + optionalString cfg.config.enable " -f ${configFile}"
            );
            Restart = "on-failure";
            RestartSec = 0;
            PrivateTmp = true;
          };
          # We need to add the application to PATH so WiVRn can find it
          path = [ applicationPath ] ++ cfg.extraPackages;
        };
      };
    };

    services = {
      # WiVRn can be used with some wired headsets so we include xr-hardware
      udev.packages = with pkgs; [
        android-udev-rules
        xr-hardware
      ];
      avahi = {
        enable = true;
        publish = {
          enable = true;
          userServices = true;
        };
      };
    };

    networking.firewall = mkIf cfg.openFirewall {
      allowedTCPPorts = [ 9757 ];
      allowedUDPPorts = [ 9757 ];
    };

    environment = {
      systemPackages = [
        cfg.package
        applicationPath
      ];
      pathsToLink = [ "/share/openxr" ];
      etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime {
        source = "${cfg.package}/share/openxr/1/openxr_wivrn.json";
      };
    };
  };
  meta.maintainers = with maintainers; [ passivelemon ];
}
+149 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  stdenv,
  fetchFromGitHub,
  fetchFromGitLab,
  applyPatches,
  autoAddDriverRunpath,
  avahi,
  boost,
  cli11,
  cmake,
  cudaPackages ? { },
  cudaSupport ? config.cudaSupport,
  eigen,
  ffmpeg,
  freetype,
  git,
  glm,
  glslang,
  harfbuzz,
  libdrm,
  libGL,
  libva,
  libpulseaudio,
  libX11,
  libXrandr,
  nix-update-script,
  nlohmann_json,
  onnxruntime,
  openxr-loader,
  pipewire,
  pkg-config,
  python3,
  shaderc,
  spdlog,
  systemd,
  udev,
  vulkan-headers,
  vulkan-loader,
  vulkan-tools,
  x264,
}:
stdenv.mkDerivation (finalAttrs: {
  pname = "wivrn";
  version = "0.19";

  src = fetchFromGitHub {
    owner = "wivrn";
    repo = "wivrn";
    rev = "v${finalAttrs.version}";
    hash = "sha256-DYV+JUWjjhLZLq+4Hv7jxOyxDqQut/mU1X0ZFMoNkDI=";
  };

  monado = applyPatches {
    src = fetchFromGitLab {
      domain = "gitlab.freedesktop.org";
      owner = "monado";
      repo = "monado";
      rev = "bcbe19ddd795f182df42051e5495e9727db36c1c";
      hash = "sha256-sh5slHROcuC3Dgenu1+hm8U5lUOW48JUbiluYvc3NiQ=";
    };

    patches = [
      "${finalAttrs.src}/patches/monado/0001-c-multi-disable-dropping-of-old-frames.patch"
      "${finalAttrs.src}/patches/monado/0002-ipc-server-Always-listen-to-stdin.patch"
      "${finalAttrs.src}/patches/monado/0003-c-multi-Don-t-log-frame-time-diff.patch"
      "${finalAttrs.src}/patches/monado/0005-distortion-images.patch"
      "${finalAttrs.src}/patches/monado/0008-Use-mipmaps-for-distortion-shader.patch"
      "${finalAttrs.src}/patches/monado/0009-convert-to-YCbCr-in-monado.patch"
    ];
  };

  strictDeps = true;

  postUnpack = ''
    # Let's make sure our monado source revision matches what is used by WiVRn upstream
    ourMonadoRev="${finalAttrs.monado.src.rev}"
    theirMonadoRev=$(grep "GIT_TAG" ${finalAttrs.src.name}/CMakeLists.txt | awk '{print $2}')
    if [ ! "$theirMonadoRev" == "$ourMonadoRev" ]; then
      echo "Our Monado source revision doesn't match CMakeLists.txt." >&2
      echo "  theirs: $theirMonadoRev" >&2
      echo "    ours: $ourMonadoRev" >&2
      return 1
    fi
  '';

  nativeBuildInputs = [
    cmake
    git
    glslang
    pkg-config
    python3
  ] ++ lib.optionals cudaSupport [ autoAddDriverRunpath ];

  buildInputs = [
    avahi
    boost
    cli11
    eigen
    ffmpeg
    freetype
    glm
    harfbuzz
    libdrm
    libGL
    libva
    libX11
    libXrandr
    libpulseaudio
    nlohmann_json
    onnxruntime
    openxr-loader
    pipewire
    shaderc
    spdlog
    systemd
    udev
    vulkan-headers
    vulkan-loader
    vulkan-tools
    x264
  ] ++ lib.optionals cudaSupport [ cudaPackages.cudatoolkit ];

  cmakeFlags = [
    (lib.cmakeBool "WIVRN_USE_VAAPI" true)
    (lib.cmakeBool "WIVRN_USE_X264" true)
    (lib.cmakeBool "WIVRN_USE_NVENC" cudaSupport)
    (lib.cmakeBool "WIVRN_USE_SYSTEMD" true)
    (lib.cmakeBool "WIVRN_USE_PIPEWIRE" true)
    (lib.cmakeBool "WIVRN_USE_PULSEAUDIO" true)
    (lib.cmakeBool "WIVRN_BUILD_CLIENT" false)
    (lib.cmakeBool "WIVRN_OPENXR_INSTALL_ABSOLUTE_RUNTIME_PATH" true)
    (lib.cmakeBool "FETCHCONTENT_FULLY_DISCONNECTED" true)
    (lib.cmakeFeature "FETCHCONTENT_SOURCE_DIR_MONADO" "${finalAttrs.monado}")
  ];

  passthru.updateScript = nix-update-script { };

  meta = with lib; {
    description = "An OpenXR streaming application to a standalone headset";
    homepage = "https://github.com/Meumeu/WiVRn/";
    changelog = "https://github.com/Meumeu/WiVRn/releases/";
    license = licenses.gpl3Only;
    maintainers = with maintainers; [ passivelemon ];
    platforms = platforms.linux;
    mainProgram = "wivrn-server";
  };
})