mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-26 00:43:20 +00:00
Merge pull request #234615 from linsui/dconf
nixos/dconf: support generating from attrs
This commit is contained in:
commit
434d160d7c
@ -21,6 +21,7 @@ let
|
||||
{ name = "filesystem"; description = "filesystem functions"; }
|
||||
{ name = "sources"; description = "source filtering functions"; }
|
||||
{ name = "cli"; description = "command-line serialization functions"; }
|
||||
{ name = "gvariant"; description = "GVariant formatted string serialization functions"; }
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -41,6 +41,7 @@ let
|
||||
|
||||
# serialization
|
||||
cli = callLibs ./cli.nix;
|
||||
gvariant = callLibs ./gvariant.nix;
|
||||
generators = callLibs ./generators.nix;
|
||||
|
||||
# misc
|
||||
|
@ -230,6 +230,14 @@ rec {
|
||||
in
|
||||
toINI_ (gitFlattenAttrs attrs);
|
||||
|
||||
# mkKeyValueDefault wrapper that handles dconf INI quirks.
|
||||
# The main differences of the format is that it requires strings to be quoted.
|
||||
mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (lib.gvariant.mkValue v); } "=";
|
||||
|
||||
# Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en
|
||||
# for details.
|
||||
toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; };
|
||||
|
||||
/* Generates JSON from an arbitrary (non-function) value.
|
||||
* For more information see the documentation of the builtin.
|
||||
*/
|
||||
|
290
lib/gvariant.nix
Normal file
290
lib/gvariant.nix
Normal file
@ -0,0 +1,290 @@
|
||||
# This file is based on https://github.com/nix-community/home-manager
|
||||
# Copyright (c) 2017-2022 Home Manager contributors
|
||||
#
|
||||
|
||||
|
||||
{ lib }:
|
||||
|
||||
/* A partial and basic implementation of GVariant formatted strings.
|
||||
See https://docs.gtk.org/glib/gvariant-format-strings.html for detauls.
|
||||
|
||||
Note, this API is not considered fully stable and it might therefore
|
||||
change in backwards incompatible ways without prior notice.
|
||||
*/
|
||||
let
|
||||
inherit (lib)
|
||||
concatMapStringsSep concatStrings escape head replaceStrings;
|
||||
|
||||
mkPrimitive = t: v: {
|
||||
_type = "gvariant";
|
||||
type = t;
|
||||
value = v;
|
||||
__toString = self: "@${self.type} ${toString self.value}"; # https://docs.gtk.org/glib/gvariant-text.html
|
||||
};
|
||||
|
||||
type = {
|
||||
arrayOf = t: "a${t}";
|
||||
maybeOf = t: "m${t}";
|
||||
tupleOf = ts: "(${concatStrings ts})";
|
||||
dictionaryEntryOf = nameType: valueType: "{${nameType}${valueType}}";
|
||||
string = "s";
|
||||
boolean = "b";
|
||||
uchar = "y";
|
||||
int16 = "n";
|
||||
uint16 = "q";
|
||||
int32 = "i";
|
||||
uint32 = "u";
|
||||
int64 = "x";
|
||||
uint64 = "t";
|
||||
double = "d";
|
||||
variant = "v";
|
||||
};
|
||||
|
||||
/* Check if a value is a GVariant value
|
||||
|
||||
Type:
|
||||
isGVariant :: Any -> Bool
|
||||
*/
|
||||
isGVariant = v: v._type or "" == "gvariant";
|
||||
|
||||
in
|
||||
rec {
|
||||
|
||||
inherit type isGVariant;
|
||||
|
||||
/* Returns the GVariant value that most closely matches the given Nix value.
|
||||
If no GVariant value can be found unambiguously then error is thrown.
|
||||
|
||||
Type:
|
||||
mkValue :: Any -> gvariant
|
||||
*/
|
||||
mkValue = v:
|
||||
if builtins.isBool v then
|
||||
mkBoolean v
|
||||
else if builtins.isFloat v then
|
||||
mkDouble v
|
||||
else if builtins.isString v then
|
||||
mkString v
|
||||
else if builtins.isList v then
|
||||
mkArray v
|
||||
else if isGVariant v then
|
||||
v
|
||||
else
|
||||
throw "The GVariant type of ${v} can't be inferred.";
|
||||
|
||||
/* Returns the GVariant array from the given type of the elements and a Nix list.
|
||||
|
||||
Type:
|
||||
mkArray :: [Any] -> gvariant
|
||||
|
||||
Example:
|
||||
# Creating a string array
|
||||
lib.gvariant.mkArray [ "a" "b" "c" ]
|
||||
*/
|
||||
mkArray = elems:
|
||||
let
|
||||
vs = map mkValue (lib.throwIf (elems == [ ]) "Please create empty array with mkEmptyArray." elems);
|
||||
elemType = lib.throwIfNot (lib.all (t: (head vs).type == t) (map (v: v.type) vs))
|
||||
"Elements in a list should have same type."
|
||||
(head vs).type;
|
||||
in
|
||||
mkPrimitive (type.arrayOf elemType) vs // {
|
||||
__toString = self:
|
||||
"@${self.type} [${concatMapStringsSep "," toString self.value}]";
|
||||
};
|
||||
|
||||
/* Returns the GVariant array from the given empty Nix list.
|
||||
|
||||
Type:
|
||||
mkEmptyArray :: gvariant.type -> gvariant
|
||||
|
||||
Example:
|
||||
# Creating an empty string array
|
||||
lib.gvariant.mkEmptyArray (lib.gvariant.type.string)
|
||||
*/
|
||||
mkEmptyArray = elemType: mkPrimitive (type.arrayOf elemType) [ ] // {
|
||||
__toString = self: "@${self.type} []";
|
||||
};
|
||||
|
||||
|
||||
/* Returns the GVariant variant from the given Nix value. Variants are containers
|
||||
of different GVariant type.
|
||||
|
||||
Type:
|
||||
mkVariant :: Any -> gvariant
|
||||
|
||||
Example:
|
||||
lib.gvariant.mkArray [
|
||||
(lib.gvariant.mkVariant "a string")
|
||||
(lib.gvariant.mkVariant (lib.gvariant.mkInt32 1))
|
||||
]
|
||||
*/
|
||||
mkVariant = elem:
|
||||
let gvarElem = mkValue elem;
|
||||
in mkPrimitive type.variant gvarElem // {
|
||||
__toString = self: "<${toString self.value}>";
|
||||
};
|
||||
|
||||
/* Returns the GVariant dictionary entry from the given key and value.
|
||||
|
||||
Type:
|
||||
mkDictionaryEntry :: String -> Any -> gvariant
|
||||
|
||||
Example:
|
||||
# A dictionary describing an Epiphany’s search provider
|
||||
[
|
||||
(lib.gvariant.mkDictionaryEntry "url" (lib.gvariant.mkVariant "https://duckduckgo.com/?q=%s&t=epiphany"))
|
||||
(lib.gvariant.mkDictionaryEntry "bang" (lib.gvariant.mkVariant "!d"))
|
||||
(lib.gvariant.mkDictionaryEntry "name" (lib.gvariant.mkVariant "DuckDuckGo"))
|
||||
]
|
||||
*/
|
||||
mkDictionaryEntry =
|
||||
# The key of the entry
|
||||
name:
|
||||
# The value of the entry
|
||||
value:
|
||||
let
|
||||
name' = mkValue name;
|
||||
value' = mkValue value;
|
||||
dictionaryType = type.dictionaryEntryOf name'.type value'.type;
|
||||
in
|
||||
mkPrimitive dictionaryType { inherit name value; } // {
|
||||
__toString = self: "@${self.type} {${name'},${value'}}";
|
||||
};
|
||||
|
||||
/* Returns the GVariant maybe from the given element type.
|
||||
|
||||
Type:
|
||||
mkMaybe :: gvariant.type -> Any -> gvariant
|
||||
*/
|
||||
mkMaybe = elemType: elem:
|
||||
mkPrimitive (type.maybeOf elemType) elem // {
|
||||
__toString = self:
|
||||
if self.value == null then
|
||||
"@${self.type} nothing"
|
||||
else
|
||||
"just ${toString self.value}";
|
||||
};
|
||||
|
||||
/* Returns the GVariant nothing from the given element type.
|
||||
|
||||
Type:
|
||||
mkNothing :: gvariant.type -> gvariant
|
||||
*/
|
||||
mkNothing = elemType: mkMaybe elemType null;
|
||||
|
||||
/* Returns the GVariant just from the given Nix value.
|
||||
|
||||
Type:
|
||||
mkJust :: Any -> gvariant
|
||||
*/
|
||||
mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;
|
||||
|
||||
/* Returns the GVariant tuple from the given Nix list.
|
||||
|
||||
Type:
|
||||
mkTuple :: [Any] -> gvariant
|
||||
*/
|
||||
mkTuple = elems:
|
||||
let
|
||||
gvarElems = map mkValue elems;
|
||||
tupleType = type.tupleOf (map (e: e.type) gvarElems);
|
||||
in
|
||||
mkPrimitive tupleType gvarElems // {
|
||||
__toString = self:
|
||||
"@${self.type} (${concatMapStringsSep "," toString self.value})";
|
||||
};
|
||||
|
||||
/* Returns the GVariant boolean from the given Nix bool value.
|
||||
|
||||
Type:
|
||||
mkBoolean :: Bool -> gvariant
|
||||
*/
|
||||
mkBoolean = v:
|
||||
mkPrimitive type.boolean v // {
|
||||
__toString = self: if self.value then "true" else "false";
|
||||
};
|
||||
|
||||
/* Returns the GVariant string from the given Nix string value.
|
||||
|
||||
Type:
|
||||
mkString :: String -> gvariant
|
||||
*/
|
||||
mkString = v:
|
||||
let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s);
|
||||
in mkPrimitive type.string v // {
|
||||
__toString = self: "'${sanitize self.value}'";
|
||||
};
|
||||
|
||||
/* Returns the GVariant object path from the given Nix string value.
|
||||
|
||||
Type:
|
||||
mkObjectpath :: String -> gvariant
|
||||
*/
|
||||
mkObjectpath = v:
|
||||
mkPrimitive type.string v // {
|
||||
__toString = self: "objectpath '${escape [ "'" ] self.value}'";
|
||||
};
|
||||
|
||||
/* Returns the GVariant uchar from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkUchar :: Int -> gvariant
|
||||
*/
|
||||
mkUchar = mkPrimitive type.uchar;
|
||||
|
||||
/* Returns the GVariant int16 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkInt16 :: Int -> gvariant
|
||||
*/
|
||||
mkInt16 = mkPrimitive type.int16;
|
||||
|
||||
/* Returns the GVariant uint16 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkUint16 :: Int -> gvariant
|
||||
*/
|
||||
mkUint16 = mkPrimitive type.uint16;
|
||||
|
||||
/* Returns the GVariant int32 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkInt32 :: Int -> gvariant
|
||||
*/
|
||||
mkInt32 = v:
|
||||
mkPrimitive type.int32 v // {
|
||||
__toString = self: toString self.value;
|
||||
};
|
||||
|
||||
/* Returns the GVariant uint32 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkUint32 :: Int -> gvariant
|
||||
*/
|
||||
mkUint32 = mkPrimitive type.uint32;
|
||||
|
||||
/* Returns the GVariant int64 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkInt64 :: Int -> gvariant
|
||||
*/
|
||||
mkInt64 = mkPrimitive type.int64;
|
||||
|
||||
/* Returns the GVariant uint64 from the given Nix int value.
|
||||
|
||||
Type:
|
||||
mkUint64 :: Int -> gvariant
|
||||
*/
|
||||
mkUint64 = mkPrimitive type.uint64;
|
||||
|
||||
/* Returns the GVariant double from the given Nix float value.
|
||||
|
||||
Type:
|
||||
mkDouble :: Float -> gvariant
|
||||
*/
|
||||
mkDouble = v:
|
||||
mkPrimitive type.double v // {
|
||||
__toString = self: toString self.value;
|
||||
};
|
||||
}
|
93
lib/tests/modules/gvariant.nix
Normal file
93
lib/tests/modules/gvariant.nix
Normal file
@ -0,0 +1,93 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let inherit (lib) concatStringsSep mapAttrsToList mkMerge mkOption types gvariant;
|
||||
in {
|
||||
options.examples = mkOption { type = types.attrsOf gvariant; };
|
||||
|
||||
config = {
|
||||
examples = with gvariant;
|
||||
mkMerge [
|
||||
{ bool = true; }
|
||||
{ bool = true; }
|
||||
|
||||
{ float = 3.14; }
|
||||
|
||||
{ int32 = mkInt32 (- 42); }
|
||||
{ int32 = mkInt32 (- 42); }
|
||||
|
||||
{ uint32 = mkUint32 42; }
|
||||
{ uint32 = mkUint32 42; }
|
||||
|
||||
{ int16 = mkInt16 (-42); }
|
||||
{ int16 = mkInt16 (-42); }
|
||||
|
||||
{ uint16 = mkUint16 42; }
|
||||
{ uint16 = mkUint16 42; }
|
||||
|
||||
{ int64 = mkInt64 (-42); }
|
||||
{ int64 = mkInt64 (-42); }
|
||||
|
||||
{ uint64 = mkUint64 42; }
|
||||
{ uint64 = mkUint64 42; }
|
||||
|
||||
{ array1 = [ "one" ]; }
|
||||
{ array1 = mkArray [ "two" ]; }
|
||||
{ array2 = mkArray [ (mkInt32 1) ]; }
|
||||
{ array2 = mkArray [ (nkUint32 2) ]; }
|
||||
|
||||
{ emptyArray1 = [ ]; }
|
||||
{ emptyArray2 = mkEmptyArray type.uint32; }
|
||||
|
||||
{ string = "foo"; }
|
||||
{ string = "foo"; }
|
||||
{
|
||||
escapedString = ''
|
||||
'\
|
||||
'';
|
||||
}
|
||||
|
||||
{ tuple = mkTuple [ (mkInt32 1) [ "foo" ] ]; }
|
||||
|
||||
{ maybe1 = mkNothing type.string; }
|
||||
{ maybe2 = mkJust (mkUint32 4); }
|
||||
|
||||
{ variant1 = mkVariant "foo"; }
|
||||
{ variant2 = mkVariant 42; }
|
||||
|
||||
{ dictionaryEntry = mkDictionaryEntry (mkInt32 1) [ "foo" ]; }
|
||||
];
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = (
|
||||
let
|
||||
mkLine = n: v: "${n} = ${toString (gvariant.mkValue v)}";
|
||||
result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples);
|
||||
in
|
||||
result + "\n"
|
||||
) == ''
|
||||
array1 = @as ['one','two']
|
||||
array2 = @au [1,2]
|
||||
bool = true
|
||||
dictionaryEntry = @{ias} {1,@as ['foo']}
|
||||
emptyArray1 = @as []
|
||||
emptyArray2 = @au []
|
||||
escapedString = '\'\\\n'
|
||||
float = 3.140000
|
||||
int = -42
|
||||
int16 = @n -42
|
||||
int64 = @x -42
|
||||
maybe1 = @ms nothing
|
||||
maybe2 = just @u 4
|
||||
string = 'foo'
|
||||
tuple = @(ias) (1,@as ['foo'])
|
||||
uint16 = @q 42
|
||||
uint32 = @u 42
|
||||
uint64 = @t 42
|
||||
variant1 = @v <'foo'>
|
||||
variant2 = @v <42>
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
@ -1,55 +1,217 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.programs.dconf;
|
||||
cfgDir = pkgs.symlinkJoin {
|
||||
name = "dconf-system-config";
|
||||
paths = map (x: "${x}/etc/dconf") cfg.packages;
|
||||
postBuild = ''
|
||||
mkdir -p $out/profile
|
||||
mkdir -p $out/db
|
||||
'' + (
|
||||
concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
name: path: ''
|
||||
ln -s ${path} $out/profile/${name}
|
||||
''
|
||||
) cfg.profiles
|
||||
)
|
||||
) + ''
|
||||
${pkgs.dconf}/bin/dconf update $out/db
|
||||
'';
|
||||
|
||||
# Compile keyfiles to dconf DB
|
||||
compileDconfDb = dir: pkgs.runCommand "dconf-db"
|
||||
{
|
||||
nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
|
||||
} "dconf compile $out ${dir}";
|
||||
|
||||
# Check if dconf keyfiles are valid
|
||||
checkDconfKeyfiles = dir: pkgs.runCommand "check-dconf-keyfiles"
|
||||
{
|
||||
nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
|
||||
} ''
|
||||
if [[ -f ${dir} ]]; then
|
||||
echo "dconf keyfiles should be a directory but a file is provided: ${dir}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dconf compile db ${dir} || (
|
||||
echo "The dconf keyfiles are invalid: ${dir}"
|
||||
exit 1
|
||||
)
|
||||
cp -R ${dir} $out
|
||||
'';
|
||||
|
||||
mkAllLocks = settings: lib.flatten (
|
||||
lib.mapAttrsToList (k: v: lib.mapAttrsToList (k': _: "/${k}/${k'}") v) settings);
|
||||
|
||||
# Generate dconf DB from dconfDatabase and keyfiles
|
||||
mkDconfDb = val: compileDconfDb (pkgs.symlinkJoin {
|
||||
name = "nixos-generated-dconf-keyfiles";
|
||||
paths = [
|
||||
(pkgs.writeTextDir "nixos-generated-dconf-keyfiles" (lib.generators.toDconfINI val.settings))
|
||||
(pkgs.writeTextDir "locks/nixos-generated-dconf-locks" (lib.concatStringsSep "\n"
|
||||
(if val.lockAll then mkAllLocks val.settings else val.locks)
|
||||
))
|
||||
] ++ (map checkDconfKeyfiles val.keyfiles);
|
||||
});
|
||||
|
||||
# Check if a dconf DB file is valid. The dconf cli doesn't return 1 when it can't
|
||||
# open the database file so we have to check if the output is empty.
|
||||
checkDconfDb = file: pkgs.runCommand "check-dconf-db"
|
||||
{
|
||||
nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
|
||||
} ''
|
||||
if [[ -d ${file} ]]; then
|
||||
echo "dconf DB should be a file but a directory is provided: ${file}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "file-db:${file}" > profile
|
||||
DCONF_PROFILE=$(pwd)/profile dconf dump / > output 2> error
|
||||
if [[ ! -s output ]] && [[ -s error ]]; then
|
||||
cat error
|
||||
echo "The dconf DB file is invalid: ${file}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ${file} $out
|
||||
'';
|
||||
|
||||
# Generate dconf profile
|
||||
mkDconfProfile = name: value:
|
||||
if lib.isDerivation value || lib.isPath value then
|
||||
pkgs.runCommand "dconf-profile" { } ''
|
||||
if [[ -d ${value} ]]; then
|
||||
echo "Dconf profile should be a file but a directory is provided."
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p $out/etc/dconf/profile/
|
||||
cp ${value} $out/etc/dconf/profile/${name}
|
||||
''
|
||||
else
|
||||
pkgs.writeTextDir "etc/dconf/profile/${name}" (
|
||||
lib.concatMapStrings (x: "${x}\n") ((
|
||||
lib.optional value.enableUserDb "user-db:user"
|
||||
) ++ (
|
||||
map
|
||||
(value:
|
||||
let
|
||||
db = if lib.isAttrs value && !lib.isDerivation value then mkDconfDb value else checkDconfDb value;
|
||||
in
|
||||
"file-db:${db}")
|
||||
value.databases
|
||||
))
|
||||
);
|
||||
|
||||
dconfDatabase = with lib.types; submodule {
|
||||
options = {
|
||||
keyfiles = lib.mkOption {
|
||||
type = listOf (oneOf [ path package ]);
|
||||
default = [ ];
|
||||
description = lib.mdDoc "A list of dconf keyfile directories.";
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
description = lib.mdDoc "An attrset used to generate dconf keyfile.";
|
||||
example = literalExpression ''
|
||||
with lib.gvariant;
|
||||
{
|
||||
"com/raggesilver/BlackBox" = {
|
||||
scrollback-lines = mkUint32 10000;
|
||||
theme-dark = "Tommorow Night";
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
locks = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [ ];
|
||||
description = lib.mdDoc ''
|
||||
A list of dconf keys to be lockdown. This doesn't take effect if `lockAll`
|
||||
is set.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[ "/org/gnome/desktop/background/picture-uri" ]
|
||||
'';
|
||||
};
|
||||
lockAll = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = lib.mdDoc "Lockdown all dconf keys in `settings`.";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
programs.dconf = {
|
||||
enable = mkEnableOption (lib.mdDoc "dconf");
|
||||
|
||||
profiles = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
description = lib.mdDoc "Set of dconf profile files, installed at {file}`/etc/dconf/profiles/«name»`.";
|
||||
internal = true;
|
||||
dconfProfile = with lib.types; submodule {
|
||||
options = {
|
||||
enableUserDb = lib.mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = lib.mdDoc "Add `user-db:user` at the beginning of the profile.";
|
||||
};
|
||||
|
||||
packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
databases = lib.mkOption {
|
||||
type = with lib.types; listOf (oneOf [
|
||||
path
|
||||
package
|
||||
dconfDatabase
|
||||
]);
|
||||
default = [ ];
|
||||
description = lib.mdDoc ''
|
||||
List of data sources for the profile. An element can be an attrset,
|
||||
or the path of an already compiled database. Each element is converted
|
||||
to a file-db.
|
||||
|
||||
A key is searched from up to down and the first result takes the
|
||||
priority. If a lock for a particular key is installed then the value from
|
||||
the last database in the profile where the key is locked will be used.
|
||||
This can be used to enforce mandatory settings.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
programs.dconf = {
|
||||
enable = lib.mkEnableOption (lib.mdDoc "dconf");
|
||||
|
||||
profiles = lib.mkOption {
|
||||
type = with lib.types; attrsOf (oneOf [
|
||||
path
|
||||
package
|
||||
dconfProfile
|
||||
]);
|
||||
default = { };
|
||||
description = lib.mdDoc ''
|
||||
Attrset of dconf profiles. By default the `user` profile is used which
|
||||
ends up in `/etc/dconf/profile/user`.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
# A "user" profile with a database
|
||||
user.databases = [
|
||||
{
|
||||
settings = { };
|
||||
}
|
||||
];
|
||||
# A "bar" profile from a package
|
||||
bar = pkgs.bar-dconf-profile;
|
||||
# A "foo" profile from a path
|
||||
foo = ''${./foo}
|
||||
};
|
||||
'';
|
||||
};
|
||||
|
||||
packages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = [ ];
|
||||
description = lib.mdDoc "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
config = lib.mkIf (cfg.profiles != { } || cfg.enable) {
|
||||
programs.dconf.packages = lib.mapAttrsToList mkDconfProfile cfg.profiles;
|
||||
|
||||
config = mkIf (cfg.profiles != {} || cfg.enable) {
|
||||
environment.etc.dconf = mkIf (cfg.profiles != {} || cfg.packages != []) {
|
||||
source = cfgDir;
|
||||
environment.etc.dconf = lib.mkIf (cfg.packages != [ ]) {
|
||||
source = pkgs.symlinkJoin {
|
||||
name = "dconf-system-config";
|
||||
paths = map (x: "${x}/etc/dconf") cfg.packages;
|
||||
nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
|
||||
postBuild = ''
|
||||
if test -d $out/db; then
|
||||
dconf update $out/db
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.dbus.packages = [ pkgs.dconf ];
|
||||
@ -59,8 +221,9 @@ in
|
||||
# For dconf executable
|
||||
environment.systemPackages = [ pkgs.dconf ];
|
||||
|
||||
# Needed for unwrapped applications
|
||||
environment.sessionVariables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.dconf.lib}/lib/gio/modules" ];
|
||||
environment.sessionVariables = lib.mkIf cfg.enable {
|
||||
# Needed for unwrapped applications
|
||||
GIO_EXTRA_MODULES = [ "${pkgs.dconf.lib}/lib/gio/modules" ];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -231,40 +231,14 @@ in
|
||||
|
||||
systemd.user.services.dbus.wantedBy = [ "default.target" ];
|
||||
|
||||
programs.dconf.profiles.gdm =
|
||||
let
|
||||
customDconf = pkgs.writeTextFile {
|
||||
name = "gdm-dconf";
|
||||
destination = "/dconf/gdm-custom";
|
||||
text = ''
|
||||
${optionalString (!cfg.gdm.autoSuspend) ''
|
||||
[org/gnome/settings-daemon/plugins/power]
|
||||
sleep-inactive-ac-type='nothing'
|
||||
sleep-inactive-battery-type='nothing'
|
||||
sleep-inactive-ac-timeout=0
|
||||
sleep-inactive-battery-timeout=0
|
||||
''}
|
||||
'';
|
||||
programs.dconf.profiles.gdm.databases = lib.optionals (!cfg.gdm.autoSuspend) [{
|
||||
settings."org/gnome/settings-daemon/plugins/power" = {
|
||||
sleep-inactive-ac-type = "nothing";
|
||||
sleep-inactive-battery-type = "nothing";
|
||||
sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0;
|
||||
sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0;
|
||||
};
|
||||
|
||||
customDconfDb = pkgs.stdenv.mkDerivation {
|
||||
name = "gdm-dconf-db";
|
||||
buildCommand = ''
|
||||
${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf
|
||||
'';
|
||||
};
|
||||
in pkgs.stdenv.mkDerivation {
|
||||
name = "dconf-gdm-profile";
|
||||
buildCommand = ''
|
||||
# Check that the GDM profile starts with what we expect.
|
||||
if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then
|
||||
echo "GDM dconf profile changed, please update gdm.nix"
|
||||
exit 1
|
||||
fi
|
||||
# Insert our custom DB behind it.
|
||||
sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out
|
||||
'';
|
||||
};
|
||||
}] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ];
|
||||
|
||||
# Use AutomaticLogin if delay is zero, because it's immediate.
|
||||
# Otherwise with TimedLogin with zero seconds the prompt is still
|
||||
|
@ -210,6 +210,7 @@ in {
|
||||
custom-ca = handleTest ./custom-ca.nix {};
|
||||
croc = handleTest ./croc.nix {};
|
||||
darling = handleTest ./darling.nix {};
|
||||
dconf = handleTest ./dconf.nix {};
|
||||
deepin = handleTest ./deepin.nix {};
|
||||
deluge = handleTest ./deluge.nix {};
|
||||
dendrite = handleTest ./matrix/dendrite.nix {};
|
||||
|
34
nixos/tests/dconf.nix
Normal file
34
nixos/tests/dconf.nix
Normal file
@ -0,0 +1,34 @@
|
||||
import ./make-test-python.nix
|
||||
({ lib, ... }:
|
||||
{
|
||||
name = "dconf";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
linsui
|
||||
];
|
||||
|
||||
nodes.machine = { config, pkgs, lib, ... }: {
|
||||
users.extraUsers.alice = { isNormalUser = true; };
|
||||
programs.dconf = with lib.gvariant; {
|
||||
enable = true;
|
||||
profiles.user.databases = [
|
||||
{
|
||||
settings = {
|
||||
"test/not/locked" = mkInt32 1;
|
||||
"test/is/locked" = "locked";
|
||||
};
|
||||
locks = [
|
||||
"/test/is/locked"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.succeed("test $(dconf read -d /test/not/locked) == 1")
|
||||
machine.succeed("test $(dconf read -d /test/is/locked) == \"'locked'\"")
|
||||
machine.fail("sudo -u alice dbus-run-session -- dconf write /test/is/locked \"@s 'unlocked'\"")
|
||||
machine.succeed("sudo -u alice dbus-run-session -- dconf write /test/not/locked \"@i 2\"")
|
||||
'';
|
||||
})
|
@ -1,4 +1,5 @@
|
||||
{ lib, stdenv
|
||||
{ lib
|
||||
, stdenv
|
||||
, fetchurl
|
||||
, fetchpatch
|
||||
, substituteAll
|
||||
@ -8,7 +9,6 @@
|
||||
, pkg-config
|
||||
, glib
|
||||
, itstool
|
||||
, libxml2
|
||||
, xorg
|
||||
, accountsservice
|
||||
, libX11
|
||||
@ -24,12 +24,12 @@
|
||||
, audit
|
||||
, gobject-introspection
|
||||
, plymouth
|
||||
, librsvg
|
||||
, coreutils
|
||||
, xorgserver
|
||||
, xwayland
|
||||
, dbus
|
||||
, nixos-icons
|
||||
, runCommand
|
||||
}:
|
||||
|
||||
let
|
||||
@ -41,21 +41,21 @@ let
|
||||
|
||||
in
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "gdm";
|
||||
version = "44.1";
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
src = fetchurl {
|
||||
url = "mirror://gnome/sources/gdm/${lib.versions.major version}/${pname}-${version}.tar.xz";
|
||||
url = "mirror://gnome/sources/gdm/${lib.versions.major finalAttrs.version}/${finalAttrs.pname}-${finalAttrs.version}.tar.xz";
|
||||
sha256 = "aCZrOr59KPxGnQBnqsnF2rsMp5UswffCOKBJUfPcWw0=";
|
||||
};
|
||||
|
||||
mesonFlags = [
|
||||
"-Dgdm-xsession=true"
|
||||
# TODO: Setup a default-path? https://gitlab.gnome.org/GNOME/gdm/-/blob/6fc40ac6aa37c8ad87c32f0b1a5d813d34bf7770/meson_options.txt#L6
|
||||
"-Dinitial-vt=${passthru.initialVT}"
|
||||
"-Dinitial-vt=${finalAttrs.passthru.initialVT}"
|
||||
"-Dudev-dir=${placeholder "out"}/lib/udev/rules.d"
|
||||
"-Dsystemdsystemunitdir=${placeholder "out"}/lib/systemd/system"
|
||||
"-Dsystemduserunitdir=${placeholder "out"}/lib/systemd/user"
|
||||
@ -131,21 +131,21 @@ stdenv.mkDerivation rec {
|
||||
'';
|
||||
|
||||
preInstall = ''
|
||||
install -D ${override} ${DESTDIR}/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override
|
||||
install -D ${override} $DESTDIR/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
# Move stuff from DESTDIR to proper location.
|
||||
# We use rsync to merge the directories.
|
||||
rsync --archive "${DESTDIR}/etc" "$out"
|
||||
rm --recursive "${DESTDIR}/etc"
|
||||
rsync --archive "$DESTDIR/etc" "$out"
|
||||
rm --recursive "$DESTDIR/etc"
|
||||
for o in $(getAllOutputNames); do
|
||||
if [[ "$o" = "debug" ]]; then continue; fi
|
||||
rsync --archive "${DESTDIR}/''${!o}" "$(dirname "''${!o}")"
|
||||
rm --recursive "${DESTDIR}/''${!o}"
|
||||
rsync --archive "$DESTDIR/''${!o}" "$(dirname "''${!o}")"
|
||||
rm --recursive "$DESTDIR/''${!o}"
|
||||
done
|
||||
# Ensure the DESTDIR is removed.
|
||||
rmdir "${DESTDIR}/nix/store" "${DESTDIR}/nix" "${DESTDIR}"
|
||||
rmdir "$DESTDIR/nix/store" "$DESTDIR/nix" "$DESTDIR"
|
||||
|
||||
# We are setting DESTDIR so the post-install script does not compile the schemas.
|
||||
glib-compile-schemas "$out/share/glib-2.0/schemas"
|
||||
@ -170,6 +170,18 @@ stdenv.mkDerivation rec {
|
||||
# Used in GDM NixOS module
|
||||
# Don't remove.
|
||||
initialVT = "7";
|
||||
dconfDb = "${finalAttrs.finalPackage}/share/gdm/greeter-dconf-defaults";
|
||||
dconfProfile = "user-db:user\nfile-db:${finalAttrs.passthru.dconfDb}";
|
||||
|
||||
tests = {
|
||||
profile = runCommand "gdm-profile-test" { } ''
|
||||
if test "${finalAttrs.passthru.dconfProfile}" != "$(cat ${finalAttrs.finalPackage}/share/dconf/profile/gdm)"; then
|
||||
echo "GDM dconf profile changed, please update gdm.nix"
|
||||
exit 1
|
||||
fi
|
||||
touch $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
@ -179,4 +191,4 @@ stdenv.mkDerivation rec {
|
||||
maintainers = teams.gnome.members;
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user