1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 inherit (lib)
9 mkDefault
10 mkEnableOption
11 mkIf
12 mkOption
13 mkPackageOption
14 types
15 ;
16
17 cfg = config.services.monado;
18
19 runtimeManifest = "${cfg.package}/share/openxr/1/openxr_monado.json";
20in
21{
22 options.services.monado = {
23 enable = mkEnableOption "Monado user service";
24
25 package = mkPackageOption pkgs "monado" { };
26
27 defaultRuntime = mkOption {
28 type = types.bool;
29 description = ''
30 Whether to enable Monado as the default OpenXR runtime on the system.
31
32 Note that applications can bypass this option by setting an active
33 runtime in a writable XDG_CONFIG_DIRS location like `~/.config`.
34 '';
35 default = false;
36 example = true;
37 };
38
39 forceDefaultRuntime = mkOption {
40 type = types.bool;
41 description = ''
42 Whether to ensure that Monado is the active runtime set for the current
43 user.
44
45 This replaces the file `XDG_CONFIG_HOME/openxr/1/active_runtime.json`
46 when starting the service.
47 '';
48 default = false;
49 example = true;
50 };
51
52 highPriority =
53 mkEnableOption "high priority capability for monado-service"
54 // mkOption { default = true; };
55 };
56
57 config = mkIf cfg.enable {
58 security.wrappers."monado-service" = mkIf cfg.highPriority {
59 setuid = false;
60 owner = "root";
61 group = "root";
62 # cap_sys_nice needed for asynchronous reprojection
63 capabilities = "cap_sys_nice+eip";
64 source = lib.getExe' cfg.package "monado-service";
65 };
66
67 services.udev.packages = with pkgs; [ xr-hardware ];
68
69 systemd.user = {
70 services.monado = {
71 description = "Monado XR runtime service module";
72 requires = [ "monado.socket" ];
73 conflicts = [ "monado-dev.service" ];
74
75 unitConfig.ConditionUser = "!root";
76
77 environment = {
78 # Default options
79 # https://gitlab.freedesktop.org/monado/monado/-/blob/4548e1738591d0904f8db4df8ede652ece889a76/src/xrt/targets/service/monado.in.service#L12
80 XRT_COMPOSITOR_LOG = mkDefault "debug";
81 XRT_PRINT_OPTIONS = mkDefault "on";
82 IPC_EXIT_ON_DISCONNECT = mkDefault "off";
83 # Needed to avoid libbasalt.so: cannot open shared object file: No such file or directory
84 VIT_SYSTEM_LIBRARY_PATH = mkDefault "${pkgs.basalt-monado}/lib/libbasalt.so";
85 };
86
87 preStart = mkIf cfg.forceDefaultRuntime ''
88 XDG_CONFIG_HOME="''${XDG_CONFIG_HOME:-$HOME/.config}"
89 targetDir="$XDG_CONFIG_HOME/openxr/1"
90 activeRuntimePath="$targetDir/active_runtime.json"
91
92 echo "Note: Replacing active runtime at '$activeRuntimePath'"
93 mkdir --parents "$targetDir"
94 ln --symbolic --force ${runtimeManifest} "$activeRuntimePath"
95 '';
96
97 serviceConfig = {
98 ExecStart =
99 if cfg.highPriority then
100 "${config.security.wrapperDir}/monado-service"
101 else
102 lib.getExe' cfg.package "monado-service";
103 Restart = "no";
104 };
105
106 restartTriggers = [ cfg.package ];
107 };
108
109 sockets.monado = {
110 description = "Monado XR service module connection socket";
111 conflicts = [ "monado-dev.service" ];
112
113 unitConfig.ConditionUser = "!root";
114
115 socketConfig = {
116 ListenStream = "%t/monado_comp_ipc";
117 RemoveOnStop = true;
118
119 # If Monado crashes while starting up, we want to close incoming OpenXR connections
120 FlushPending = true;
121 };
122
123 restartTriggers = [ cfg.package ];
124
125 wantedBy = [ "sockets.target" ];
126 };
127 };
128
129 environment.systemPackages = [ cfg.package ];
130 environment.pathsToLink = [ "/share/openxr" ];
131
132 hardware.graphics.extraPackages = [ pkgs.monado-vulkan-layers ];
133
134 environment.etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime {
135 source = runtimeManifest;
136 };
137 };
138
139 meta.maintainers = with lib.maintainers; [ Scrumplex ];
140}