Merge pull request #234615 from linsui/dconf

nixos/dconf: support generating from attrs

authored by Jan Tojnar and committed by GitHub 434d160d b07fffd5

+660 -83
+1
doc/default.nix
··· 21 21 { name = "filesystem"; description = "filesystem functions"; } 22 22 { name = "sources"; description = "source filtering functions"; } 23 23 { name = "cli"; description = "command-line serialization functions"; } 24 + { name = "gvariant"; description = "GVariant formatted string serialization functions"; } 24 25 ]; 25 26 }; 26 27
+1
lib/default.nix
··· 41 41 42 42 # serialization 43 43 cli = callLibs ./cli.nix; 44 + gvariant = callLibs ./gvariant.nix; 44 45 generators = callLibs ./generators.nix; 45 46 46 47 # misc
+8
lib/generators.nix
··· 230 230 in 231 231 toINI_ (gitFlattenAttrs attrs); 232 232 233 + # mkKeyValueDefault wrapper that handles dconf INI quirks. 234 + # The main differences of the format is that it requires strings to be quoted. 235 + mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (lib.gvariant.mkValue v); } "="; 236 + 237 + # Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en 238 + # for details. 239 + toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; }; 240 + 233 241 /* Generates JSON from an arbitrary (non-function) value. 234 242 * For more information see the documentation of the builtin. 235 243 */
+290
lib/gvariant.nix
··· 1 + # This file is based on https://github.com/nix-community/home-manager 2 + # Copyright (c) 2017-2022 Home Manager contributors 3 + # 4 + 5 + 6 + { lib }: 7 + 8 + /* A partial and basic implementation of GVariant formatted strings. 9 + See https://docs.gtk.org/glib/gvariant-format-strings.html for detauls. 10 + 11 + Note, this API is not considered fully stable and it might therefore 12 + change in backwards incompatible ways without prior notice. 13 + */ 14 + let 15 + inherit (lib) 16 + concatMapStringsSep concatStrings escape head replaceStrings; 17 + 18 + mkPrimitive = t: v: { 19 + _type = "gvariant"; 20 + type = t; 21 + value = v; 22 + __toString = self: "@${self.type} ${toString self.value}"; # https://docs.gtk.org/glib/gvariant-text.html 23 + }; 24 + 25 + type = { 26 + arrayOf = t: "a${t}"; 27 + maybeOf = t: "m${t}"; 28 + tupleOf = ts: "(${concatStrings ts})"; 29 + dictionaryEntryOf = nameType: valueType: "{${nameType}${valueType}}"; 30 + string = "s"; 31 + boolean = "b"; 32 + uchar = "y"; 33 + int16 = "n"; 34 + uint16 = "q"; 35 + int32 = "i"; 36 + uint32 = "u"; 37 + int64 = "x"; 38 + uint64 = "t"; 39 + double = "d"; 40 + variant = "v"; 41 + }; 42 + 43 + /* Check if a value is a GVariant value 44 + 45 + Type: 46 + isGVariant :: Any -> Bool 47 + */ 48 + isGVariant = v: v._type or "" == "gvariant"; 49 + 50 + in 51 + rec { 52 + 53 + inherit type isGVariant; 54 + 55 + /* Returns the GVariant value that most closely matches the given Nix value. 56 + If no GVariant value can be found unambiguously then error is thrown. 57 + 58 + Type: 59 + mkValue :: Any -> gvariant 60 + */ 61 + mkValue = v: 62 + if builtins.isBool v then 63 + mkBoolean v 64 + else if builtins.isFloat v then 65 + mkDouble v 66 + else if builtins.isString v then 67 + mkString v 68 + else if builtins.isList v then 69 + mkArray v 70 + else if isGVariant v then 71 + v 72 + else 73 + throw "The GVariant type of ${v} can't be inferred."; 74 + 75 + /* Returns the GVariant array from the given type of the elements and a Nix list. 76 + 77 + Type: 78 + mkArray :: [Any] -> gvariant 79 + 80 + Example: 81 + # Creating a string array 82 + lib.gvariant.mkArray [ "a" "b" "c" ] 83 + */ 84 + mkArray = elems: 85 + let 86 + vs = map mkValue (lib.throwIf (elems == [ ]) "Please create empty array with mkEmptyArray." elems); 87 + elemType = lib.throwIfNot (lib.all (t: (head vs).type == t) (map (v: v.type) vs)) 88 + "Elements in a list should have same type." 89 + (head vs).type; 90 + in 91 + mkPrimitive (type.arrayOf elemType) vs // { 92 + __toString = self: 93 + "@${self.type} [${concatMapStringsSep "," toString self.value}]"; 94 + }; 95 + 96 + /* Returns the GVariant array from the given empty Nix list. 97 + 98 + Type: 99 + mkEmptyArray :: gvariant.type -> gvariant 100 + 101 + Example: 102 + # Creating an empty string array 103 + lib.gvariant.mkEmptyArray (lib.gvariant.type.string) 104 + */ 105 + mkEmptyArray = elemType: mkPrimitive (type.arrayOf elemType) [ ] // { 106 + __toString = self: "@${self.type} []"; 107 + }; 108 + 109 + 110 + /* Returns the GVariant variant from the given Nix value. Variants are containers 111 + of different GVariant type. 112 + 113 + Type: 114 + mkVariant :: Any -> gvariant 115 + 116 + Example: 117 + lib.gvariant.mkArray [ 118 + (lib.gvariant.mkVariant "a string") 119 + (lib.gvariant.mkVariant (lib.gvariant.mkInt32 1)) 120 + ] 121 + */ 122 + mkVariant = elem: 123 + let gvarElem = mkValue elem; 124 + in mkPrimitive type.variant gvarElem // { 125 + __toString = self: "<${toString self.value}>"; 126 + }; 127 + 128 + /* Returns the GVariant dictionary entry from the given key and value. 129 + 130 + Type: 131 + mkDictionaryEntry :: String -> Any -> gvariant 132 + 133 + Example: 134 + # A dictionary describing an Epiphany’s search provider 135 + [ 136 + (lib.gvariant.mkDictionaryEntry "url" (lib.gvariant.mkVariant "https://duckduckgo.com/?q=%s&t=epiphany")) 137 + (lib.gvariant.mkDictionaryEntry "bang" (lib.gvariant.mkVariant "!d")) 138 + (lib.gvariant.mkDictionaryEntry "name" (lib.gvariant.mkVariant "DuckDuckGo")) 139 + ] 140 + */ 141 + mkDictionaryEntry = 142 + # The key of the entry 143 + name: 144 + # The value of the entry 145 + value: 146 + let 147 + name' = mkValue name; 148 + value' = mkValue value; 149 + dictionaryType = type.dictionaryEntryOf name'.type value'.type; 150 + in 151 + mkPrimitive dictionaryType { inherit name value; } // { 152 + __toString = self: "@${self.type} {${name'},${value'}}"; 153 + }; 154 + 155 + /* Returns the GVariant maybe from the given element type. 156 + 157 + Type: 158 + mkMaybe :: gvariant.type -> Any -> gvariant 159 + */ 160 + mkMaybe = elemType: elem: 161 + mkPrimitive (type.maybeOf elemType) elem // { 162 + __toString = self: 163 + if self.value == null then 164 + "@${self.type} nothing" 165 + else 166 + "just ${toString self.value}"; 167 + }; 168 + 169 + /* Returns the GVariant nothing from the given element type. 170 + 171 + Type: 172 + mkNothing :: gvariant.type -> gvariant 173 + */ 174 + mkNothing = elemType: mkMaybe elemType null; 175 + 176 + /* Returns the GVariant just from the given Nix value. 177 + 178 + Type: 179 + mkJust :: Any -> gvariant 180 + */ 181 + mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem; 182 + 183 + /* Returns the GVariant tuple from the given Nix list. 184 + 185 + Type: 186 + mkTuple :: [Any] -> gvariant 187 + */ 188 + mkTuple = elems: 189 + let 190 + gvarElems = map mkValue elems; 191 + tupleType = type.tupleOf (map (e: e.type) gvarElems); 192 + in 193 + mkPrimitive tupleType gvarElems // { 194 + __toString = self: 195 + "@${self.type} (${concatMapStringsSep "," toString self.value})"; 196 + }; 197 + 198 + /* Returns the GVariant boolean from the given Nix bool value. 199 + 200 + Type: 201 + mkBoolean :: Bool -> gvariant 202 + */ 203 + mkBoolean = v: 204 + mkPrimitive type.boolean v // { 205 + __toString = self: if self.value then "true" else "false"; 206 + }; 207 + 208 + /* Returns the GVariant string from the given Nix string value. 209 + 210 + Type: 211 + mkString :: String -> gvariant 212 + */ 213 + mkString = v: 214 + let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s); 215 + in mkPrimitive type.string v // { 216 + __toString = self: "'${sanitize self.value}'"; 217 + }; 218 + 219 + /* Returns the GVariant object path from the given Nix string value. 220 + 221 + Type: 222 + mkObjectpath :: String -> gvariant 223 + */ 224 + mkObjectpath = v: 225 + mkPrimitive type.string v // { 226 + __toString = self: "objectpath '${escape [ "'" ] self.value}'"; 227 + }; 228 + 229 + /* Returns the GVariant uchar from the given Nix int value. 230 + 231 + Type: 232 + mkUchar :: Int -> gvariant 233 + */ 234 + mkUchar = mkPrimitive type.uchar; 235 + 236 + /* Returns the GVariant int16 from the given Nix int value. 237 + 238 + Type: 239 + mkInt16 :: Int -> gvariant 240 + */ 241 + mkInt16 = mkPrimitive type.int16; 242 + 243 + /* Returns the GVariant uint16 from the given Nix int value. 244 + 245 + Type: 246 + mkUint16 :: Int -> gvariant 247 + */ 248 + mkUint16 = mkPrimitive type.uint16; 249 + 250 + /* Returns the GVariant int32 from the given Nix int value. 251 + 252 + Type: 253 + mkInt32 :: Int -> gvariant 254 + */ 255 + mkInt32 = v: 256 + mkPrimitive type.int32 v // { 257 + __toString = self: toString self.value; 258 + }; 259 + 260 + /* Returns the GVariant uint32 from the given Nix int value. 261 + 262 + Type: 263 + mkUint32 :: Int -> gvariant 264 + */ 265 + mkUint32 = mkPrimitive type.uint32; 266 + 267 + /* Returns the GVariant int64 from the given Nix int value. 268 + 269 + Type: 270 + mkInt64 :: Int -> gvariant 271 + */ 272 + mkInt64 = mkPrimitive type.int64; 273 + 274 + /* Returns the GVariant uint64 from the given Nix int value. 275 + 276 + Type: 277 + mkUint64 :: Int -> gvariant 278 + */ 279 + mkUint64 = mkPrimitive type.uint64; 280 + 281 + /* Returns the GVariant double from the given Nix float value. 282 + 283 + Type: 284 + mkDouble :: Float -> gvariant 285 + */ 286 + mkDouble = v: 287 + mkPrimitive type.double v // { 288 + __toString = self: toString self.value; 289 + }; 290 + }
+93
lib/tests/modules/gvariant.nix
··· 1 + { config, lib, ... }: 2 + 3 + let inherit (lib) concatStringsSep mapAttrsToList mkMerge mkOption types gvariant; 4 + in { 5 + options.examples = mkOption { type = types.attrsOf gvariant; }; 6 + 7 + config = { 8 + examples = with gvariant; 9 + mkMerge [ 10 + { bool = true; } 11 + { bool = true; } 12 + 13 + { float = 3.14; } 14 + 15 + { int32 = mkInt32 (- 42); } 16 + { int32 = mkInt32 (- 42); } 17 + 18 + { uint32 = mkUint32 42; } 19 + { uint32 = mkUint32 42; } 20 + 21 + { int16 = mkInt16 (-42); } 22 + { int16 = mkInt16 (-42); } 23 + 24 + { uint16 = mkUint16 42; } 25 + { uint16 = mkUint16 42; } 26 + 27 + { int64 = mkInt64 (-42); } 28 + { int64 = mkInt64 (-42); } 29 + 30 + { uint64 = mkUint64 42; } 31 + { uint64 = mkUint64 42; } 32 + 33 + { array1 = [ "one" ]; } 34 + { array1 = mkArray [ "two" ]; } 35 + { array2 = mkArray [ (mkInt32 1) ]; } 36 + { array2 = mkArray [ (nkUint32 2) ]; } 37 + 38 + { emptyArray1 = [ ]; } 39 + { emptyArray2 = mkEmptyArray type.uint32; } 40 + 41 + { string = "foo"; } 42 + { string = "foo"; } 43 + { 44 + escapedString = '' 45 + '\ 46 + ''; 47 + } 48 + 49 + { tuple = mkTuple [ (mkInt32 1) [ "foo" ] ]; } 50 + 51 + { maybe1 = mkNothing type.string; } 52 + { maybe2 = mkJust (mkUint32 4); } 53 + 54 + { variant1 = mkVariant "foo"; } 55 + { variant2 = mkVariant 42; } 56 + 57 + { dictionaryEntry = mkDictionaryEntry (mkInt32 1) [ "foo" ]; } 58 + ]; 59 + 60 + assertions = [ 61 + { 62 + assertion = ( 63 + let 64 + mkLine = n: v: "${n} = ${toString (gvariant.mkValue v)}"; 65 + result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples); 66 + in 67 + result + "\n" 68 + ) == '' 69 + array1 = @as ['one','two'] 70 + array2 = @au [1,2] 71 + bool = true 72 + dictionaryEntry = @{ias} {1,@as ['foo']} 73 + emptyArray1 = @as [] 74 + emptyArray2 = @au [] 75 + escapedString = '\'\\\n' 76 + float = 3.140000 77 + int = -42 78 + int16 = @n -42 79 + int64 = @x -42 80 + maybe1 = @ms nothing 81 + maybe2 = just @u 4 82 + string = 'foo' 83 + tuple = @(ias) (1,@as ['foo']) 84 + uint16 = @q 42 85 + uint32 = @u 42 86 + uint64 = @t 42 87 + variant1 = @v <'foo'> 88 + variant2 = @v <42> 89 + ''; 90 + } 91 + ]; 92 + }; 93 + }
+200 -37
nixos/modules/programs/dconf.nix
··· 1 1 { config, lib, pkgs, ... }: 2 2 3 - with lib; 4 - 5 3 let 6 4 cfg = config.programs.dconf; 7 - cfgDir = pkgs.symlinkJoin { 8 - name = "dconf-system-config"; 9 - paths = map (x: "${x}/etc/dconf") cfg.packages; 10 - postBuild = '' 11 - mkdir -p $out/profile 12 - mkdir -p $out/db 13 - '' + ( 14 - concatStringsSep "\n" ( 15 - mapAttrsToList ( 16 - name: path: '' 17 - ln -s ${path} $out/profile/${name} 18 - '' 19 - ) cfg.profiles 20 - ) 21 - ) + '' 22 - ${pkgs.dconf}/bin/dconf update $out/db 23 - ''; 5 + 6 + # Compile keyfiles to dconf DB 7 + compileDconfDb = dir: pkgs.runCommand "dconf-db" 8 + { 9 + nativeBuildInputs = [ (lib.getBin pkgs.dconf) ]; 10 + } "dconf compile $out ${dir}"; 11 + 12 + # Check if dconf keyfiles are valid 13 + checkDconfKeyfiles = dir: pkgs.runCommand "check-dconf-keyfiles" 14 + { 15 + nativeBuildInputs = [ (lib.getBin pkgs.dconf) ]; 16 + } '' 17 + if [[ -f ${dir} ]]; then 18 + echo "dconf keyfiles should be a directory but a file is provided: ${dir}" 19 + exit 1 20 + fi 21 + 22 + dconf compile db ${dir} || ( 23 + echo "The dconf keyfiles are invalid: ${dir}" 24 + exit 1 25 + ) 26 + cp -R ${dir} $out 27 + ''; 28 + 29 + mkAllLocks = settings: lib.flatten ( 30 + lib.mapAttrsToList (k: v: lib.mapAttrsToList (k': _: "/${k}/${k'}") v) settings); 31 + 32 + # Generate dconf DB from dconfDatabase and keyfiles 33 + mkDconfDb = val: compileDconfDb (pkgs.symlinkJoin { 34 + name = "nixos-generated-dconf-keyfiles"; 35 + paths = [ 36 + (pkgs.writeTextDir "nixos-generated-dconf-keyfiles" (lib.generators.toDconfINI val.settings)) 37 + (pkgs.writeTextDir "locks/nixos-generated-dconf-locks" (lib.concatStringsSep "\n" 38 + (if val.lockAll then mkAllLocks val.settings else val.locks) 39 + )) 40 + ] ++ (map checkDconfKeyfiles val.keyfiles); 41 + }); 42 + 43 + # Check if a dconf DB file is valid. The dconf cli doesn't return 1 when it can't 44 + # open the database file so we have to check if the output is empty. 45 + checkDconfDb = file: pkgs.runCommand "check-dconf-db" 46 + { 47 + nativeBuildInputs = [ (lib.getBin pkgs.dconf) ]; 48 + } '' 49 + if [[ -d ${file} ]]; then 50 + echo "dconf DB should be a file but a directory is provided: ${file}" 51 + exit 1 52 + fi 53 + 54 + echo "file-db:${file}" > profile 55 + DCONF_PROFILE=$(pwd)/profile dconf dump / > output 2> error 56 + if [[ ! -s output ]] && [[ -s error ]]; then 57 + cat error 58 + echo "The dconf DB file is invalid: ${file}" 59 + exit 1 60 + fi 61 + 62 + cp ${file} $out 63 + ''; 64 + 65 + # Generate dconf profile 66 + mkDconfProfile = name: value: 67 + if lib.isDerivation value || lib.isPath value then 68 + pkgs.runCommand "dconf-profile" { } '' 69 + if [[ -d ${value} ]]; then 70 + echo "Dconf profile should be a file but a directory is provided." 71 + exit 1 72 + fi 73 + mkdir -p $out/etc/dconf/profile/ 74 + cp ${value} $out/etc/dconf/profile/${name} 75 + '' 76 + else 77 + pkgs.writeTextDir "etc/dconf/profile/${name}" ( 78 + lib.concatMapStrings (x: "${x}\n") (( 79 + lib.optional value.enableUserDb "user-db:user" 80 + ) ++ ( 81 + map 82 + (value: 83 + let 84 + db = if lib.isAttrs value && !lib.isDerivation value then mkDconfDb value else checkDconfDb value; 85 + in 86 + "file-db:${db}") 87 + value.databases 88 + )) 89 + ); 90 + 91 + dconfDatabase = with lib.types; submodule { 92 + options = { 93 + keyfiles = lib.mkOption { 94 + type = listOf (oneOf [ path package ]); 95 + default = [ ]; 96 + description = lib.mdDoc "A list of dconf keyfile directories."; 97 + }; 98 + settings = lib.mkOption { 99 + type = attrs; 100 + default = { }; 101 + description = lib.mdDoc "An attrset used to generate dconf keyfile."; 102 + example = literalExpression '' 103 + with lib.gvariant; 104 + { 105 + "com/raggesilver/BlackBox" = { 106 + scrollback-lines = mkUint32 10000; 107 + theme-dark = "Tommorow Night"; 108 + }; 109 + } 110 + ''; 111 + }; 112 + locks = lib.mkOption { 113 + type = with lib.types; listOf str; 114 + default = [ ]; 115 + description = lib.mdDoc '' 116 + A list of dconf keys to be lockdown. This doesn't take effect if `lockAll` 117 + is set. 118 + ''; 119 + example = literalExpression '' 120 + [ "/org/gnome/desktop/background/picture-uri" ] 121 + ''; 122 + }; 123 + lockAll = lib.mkOption { 124 + type = lib.types.bool; 125 + default = false; 126 + description = lib.mdDoc "Lockdown all dconf keys in `settings`."; 127 + }; 128 + }; 129 + }; 130 + 131 + dconfProfile = with lib.types; submodule { 132 + options = { 133 + enableUserDb = lib.mkOption { 134 + type = bool; 135 + default = true; 136 + description = lib.mdDoc "Add `user-db:user` at the beginning of the profile."; 137 + }; 138 + 139 + databases = lib.mkOption { 140 + type = with lib.types; listOf (oneOf [ 141 + path 142 + package 143 + dconfDatabase 144 + ]); 145 + default = [ ]; 146 + description = lib.mdDoc '' 147 + List of data sources for the profile. An element can be an attrset, 148 + or the path of an already compiled database. Each element is converted 149 + to a file-db. 150 + 151 + A key is searched from up to down and the first result takes the 152 + priority. If a lock for a particular key is installed then the value from 153 + the last database in the profile where the key is locked will be used. 154 + This can be used to enforce mandatory settings. 155 + ''; 156 + }; 157 + }; 24 158 }; 159 + 25 160 in 26 161 { 27 - ###### interface 28 - 29 162 options = { 30 163 programs.dconf = { 31 - enable = mkEnableOption (lib.mdDoc "dconf"); 164 + enable = lib.mkEnableOption (lib.mdDoc "dconf"); 32 165 33 - profiles = mkOption { 34 - type = types.attrsOf types.path; 35 - default = {}; 36 - description = lib.mdDoc "Set of dconf profile files, installed at {file}`/etc/dconf/profiles/«name»`."; 37 - internal = true; 166 + profiles = lib.mkOption { 167 + type = with lib.types; attrsOf (oneOf [ 168 + path 169 + package 170 + dconfProfile 171 + ]); 172 + default = { }; 173 + description = lib.mdDoc '' 174 + Attrset of dconf profiles. By default the `user` profile is used which 175 + ends up in `/etc/dconf/profile/user`. 176 + ''; 177 + example = lib.literalExpression '' 178 + { 179 + # A "user" profile with a database 180 + user.databases = [ 181 + { 182 + settings = { }; 183 + } 184 + ]; 185 + # A "bar" profile from a package 186 + bar = pkgs.bar-dconf-profile; 187 + # A "foo" profile from a path 188 + foo = ''${./foo} 189 + }; 190 + ''; 38 191 }; 39 192 40 - packages = mkOption { 41 - type = types.listOf types.package; 42 - default = []; 193 + packages = lib.mkOption { 194 + type = lib.types.listOf lib.types.package; 195 + default = [ ]; 43 196 description = lib.mdDoc "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`."; 44 197 }; 45 198 }; 46 199 }; 47 200 48 - ###### implementation 201 + config = lib.mkIf (cfg.profiles != { } || cfg.enable) { 202 + programs.dconf.packages = lib.mapAttrsToList mkDconfProfile cfg.profiles; 49 203 50 - config = mkIf (cfg.profiles != {} || cfg.enable) { 51 - environment.etc.dconf = mkIf (cfg.profiles != {} || cfg.packages != []) { 52 - source = cfgDir; 204 + environment.etc.dconf = lib.mkIf (cfg.packages != [ ]) { 205 + source = pkgs.symlinkJoin { 206 + name = "dconf-system-config"; 207 + paths = map (x: "${x}/etc/dconf") cfg.packages; 208 + nativeBuildInputs = [ (lib.getBin pkgs.dconf) ]; 209 + postBuild = '' 210 + if test -d $out/db; then 211 + dconf update $out/db 212 + fi 213 + ''; 214 + }; 53 215 }; 54 216 55 217 services.dbus.packages = [ pkgs.dconf ]; ··· 59 221 # For dconf executable 60 222 environment.systemPackages = [ pkgs.dconf ]; 61 223 62 - # Needed for unwrapped applications 63 - environment.sessionVariables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.dconf.lib}/lib/gio/modules" ]; 224 + environment.sessionVariables = lib.mkIf cfg.enable { 225 + # Needed for unwrapped applications 226 + GIO_EXTRA_MODULES = [ "${pkgs.dconf.lib}/lib/gio/modules" ]; 227 + }; 64 228 }; 65 - 66 229 }
+7 -33
nixos/modules/services/x11/display-managers/gdm.nix
··· 231 231 232 232 systemd.user.services.dbus.wantedBy = [ "default.target" ]; 233 233 234 - programs.dconf.profiles.gdm = 235 - let 236 - customDconf = pkgs.writeTextFile { 237 - name = "gdm-dconf"; 238 - destination = "/dconf/gdm-custom"; 239 - text = '' 240 - ${optionalString (!cfg.gdm.autoSuspend) '' 241 - [org/gnome/settings-daemon/plugins/power] 242 - sleep-inactive-ac-type='nothing' 243 - sleep-inactive-battery-type='nothing' 244 - sleep-inactive-ac-timeout=0 245 - sleep-inactive-battery-timeout=0 246 - ''} 247 - ''; 248 - }; 249 - 250 - customDconfDb = pkgs.stdenv.mkDerivation { 251 - name = "gdm-dconf-db"; 252 - buildCommand = '' 253 - ${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf 254 - ''; 234 + programs.dconf.profiles.gdm.databases = lib.optionals (!cfg.gdm.autoSuspend) [{ 235 + settings."org/gnome/settings-daemon/plugins/power" = { 236 + sleep-inactive-ac-type = "nothing"; 237 + sleep-inactive-battery-type = "nothing"; 238 + sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0; 239 + sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0; 255 240 }; 256 - in pkgs.stdenv.mkDerivation { 257 - name = "dconf-gdm-profile"; 258 - buildCommand = '' 259 - # Check that the GDM profile starts with what we expect. 260 - if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then 261 - echo "GDM dconf profile changed, please update gdm.nix" 262 - exit 1 263 - fi 264 - # Insert our custom DB behind it. 265 - sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out 266 - ''; 267 - }; 241 + }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ]; 268 242 269 243 # Use AutomaticLogin if delay is zero, because it's immediate. 270 244 # Otherwise with TimedLogin with zero seconds the prompt is still
+1
nixos/tests/all-tests.nix
··· 210 210 custom-ca = handleTest ./custom-ca.nix {}; 211 211 croc = handleTest ./croc.nix {}; 212 212 darling = handleTest ./darling.nix {}; 213 + dconf = handleTest ./dconf.nix {}; 213 214 deepin = handleTest ./deepin.nix {}; 214 215 deluge = handleTest ./deluge.nix {}; 215 216 dendrite = handleTest ./matrix/dendrite.nix {};
+34
nixos/tests/dconf.nix
··· 1 + import ./make-test-python.nix 2 + ({ lib, ... }: 3 + { 4 + name = "dconf"; 5 + 6 + meta.maintainers = with lib.maintainers; [ 7 + linsui 8 + ]; 9 + 10 + nodes.machine = { config, pkgs, lib, ... }: { 11 + users.extraUsers.alice = { isNormalUser = true; }; 12 + programs.dconf = with lib.gvariant; { 13 + enable = true; 14 + profiles.user.databases = [ 15 + { 16 + settings = { 17 + "test/not/locked" = mkInt32 1; 18 + "test/is/locked" = "locked"; 19 + }; 20 + locks = [ 21 + "/test/is/locked" 22 + ]; 23 + } 24 + ]; 25 + }; 26 + }; 27 + 28 + testScript = '' 29 + machine.succeed("test $(dconf read -d /test/not/locked) == 1") 30 + machine.succeed("test $(dconf read -d /test/is/locked) == \"'locked'\"") 31 + machine.fail("sudo -u alice dbus-run-session -- dconf write /test/is/locked \"@s 'unlocked'\"") 32 + machine.succeed("sudo -u alice dbus-run-session -- dconf write /test/not/locked \"@i 2\"") 33 + ''; 34 + })
+25 -13
pkgs/desktops/gnome/core/gdm/default.nix
··· 1 - { lib, stdenv 1 + { lib 2 + , stdenv 2 3 , fetchurl 3 4 , fetchpatch 4 5 , substituteAll ··· 8 9 , pkg-config 9 10 , glib 10 11 , itstool 11 - , libxml2 12 12 , xorg 13 13 , accountsservice 14 14 , libX11 ··· 24 24 , audit 25 25 , gobject-introspection 26 26 , plymouth 27 - , librsvg 28 27 , coreutils 29 28 , xorgserver 30 29 , xwayland 31 30 , dbus 32 31 , nixos-icons 32 + , runCommand 33 33 }: 34 34 35 35 let ··· 41 41 42 42 in 43 43 44 - stdenv.mkDerivation rec { 44 + stdenv.mkDerivation (finalAttrs: { 45 45 pname = "gdm"; 46 46 version = "44.1"; 47 47 48 48 outputs = [ "out" "dev" ]; 49 49 50 50 src = fetchurl { 51 - url = "mirror://gnome/sources/gdm/${lib.versions.major version}/${pname}-${version}.tar.xz"; 51 + url = "mirror://gnome/sources/gdm/${lib.versions.major finalAttrs.version}/${finalAttrs.pname}-${finalAttrs.version}.tar.xz"; 52 52 sha256 = "aCZrOr59KPxGnQBnqsnF2rsMp5UswffCOKBJUfPcWw0="; 53 53 }; 54 54 55 55 mesonFlags = [ 56 56 "-Dgdm-xsession=true" 57 57 # TODO: Setup a default-path? https://gitlab.gnome.org/GNOME/gdm/-/blob/6fc40ac6aa37c8ad87c32f0b1a5d813d34bf7770/meson_options.txt#L6 58 - "-Dinitial-vt=${passthru.initialVT}" 58 + "-Dinitial-vt=${finalAttrs.passthru.initialVT}" 59 59 "-Dudev-dir=${placeholder "out"}/lib/udev/rules.d" 60 60 "-Dsystemdsystemunitdir=${placeholder "out"}/lib/systemd/system" 61 61 "-Dsystemduserunitdir=${placeholder "out"}/lib/systemd/user" ··· 131 131 ''; 132 132 133 133 preInstall = '' 134 - install -D ${override} ${DESTDIR}/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override 134 + install -D ${override} $DESTDIR/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override 135 135 ''; 136 136 137 137 postInstall = '' 138 138 # Move stuff from DESTDIR to proper location. 139 139 # We use rsync to merge the directories. 140 - rsync --archive "${DESTDIR}/etc" "$out" 141 - rm --recursive "${DESTDIR}/etc" 140 + rsync --archive "$DESTDIR/etc" "$out" 141 + rm --recursive "$DESTDIR/etc" 142 142 for o in $(getAllOutputNames); do 143 143 if [[ "$o" = "debug" ]]; then continue; fi 144 - rsync --archive "${DESTDIR}/''${!o}" "$(dirname "''${!o}")" 145 - rm --recursive "${DESTDIR}/''${!o}" 144 + rsync --archive "$DESTDIR/''${!o}" "$(dirname "''${!o}")" 145 + rm --recursive "$DESTDIR/''${!o}" 146 146 done 147 147 # Ensure the DESTDIR is removed. 148 - rmdir "${DESTDIR}/nix/store" "${DESTDIR}/nix" "${DESTDIR}" 148 + rmdir "$DESTDIR/nix/store" "$DESTDIR/nix" "$DESTDIR" 149 149 150 150 # We are setting DESTDIR so the post-install script does not compile the schemas. 151 151 glib-compile-schemas "$out/share/glib-2.0/schemas" ··· 170 170 # Used in GDM NixOS module 171 171 # Don't remove. 172 172 initialVT = "7"; 173 + dconfDb = "${finalAttrs.finalPackage}/share/gdm/greeter-dconf-defaults"; 174 + dconfProfile = "user-db:user\nfile-db:${finalAttrs.passthru.dconfDb}"; 175 + 176 + tests = { 177 + profile = runCommand "gdm-profile-test" { } '' 178 + if test "${finalAttrs.passthru.dconfProfile}" != "$(cat ${finalAttrs.finalPackage}/share/dconf/profile/gdm)"; then 179 + echo "GDM dconf profile changed, please update gdm.nix" 180 + exit 1 181 + fi 182 + touch $out 183 + ''; 184 + }; 173 185 }; 174 186 175 187 meta = with lib; { ··· 179 191 maintainers = teams.gnome.members; 180 192 platforms = platforms.linux; 181 193 }; 182 - } 194 + })