1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.clight;
12
13 toConf =
14 v:
15 if builtins.isFloat v then
16 toString v
17 else if isInt v then
18 toString v
19 else if isBool v then
20 boolToString v
21 else if isString v then
22 ''"${escape [ ''"'' ] v}"''
23 else if isList v then
24 "[ " + concatMapStringsSep ", " toConf v + " ]"
25 else if isAttrs v then
26 "\n{\n" + convertAttrs v + "\n}"
27 else
28 abort "clight.toConf: unexpected type (v = ${v})";
29
30 getSep = v: if isAttrs v then ":" else "=";
31
32 convertAttrs =
33 attrs:
34 concatStringsSep "\n" (
35 mapAttrsToList (name: value: "${toString name} ${getSep value} ${toConf value};") attrs
36 );
37
38 clightConf = pkgs.writeText "clight.conf" (
39 convertAttrs (filterAttrs (_: value: value != null) cfg.settings)
40 );
41in
42{
43 options.services.clight = {
44 enable = mkEnableOption "clight";
45
46 temperature = {
47 day = mkOption {
48 type = types.int;
49 default = 5500;
50 description = ''
51 Colour temperature to use during the day, between
52 `1000` and `25000` K.
53 '';
54 };
55 night = mkOption {
56 type = types.int;
57 default = 3700;
58 description = ''
59 Colour temperature to use at night, between
60 `1000` and `25000` K.
61 '';
62 };
63 };
64
65 settings =
66 let
67 validConfigTypes =
68 with types;
69 oneOf [
70 int
71 str
72 bool
73 float
74 ];
75 collectionTypes =
76 with types;
77 oneOf [
78 validConfigTypes
79 (listOf validConfigTypes)
80 ];
81 in
82 mkOption {
83 type = with types; attrsOf (nullOr (either collectionTypes (attrsOf collectionTypes)));
84 default = { };
85 example = {
86 captures = 20;
87 gamma_long_transition = true;
88 ac_capture_timeouts = [
89 120
90 300
91 60
92 ];
93 };
94 description = ''
95 Additional configuration to extend clight.conf. See
96 <https://github.com/FedeDP/Clight/blob/master/Extra/clight.conf> for a
97 sample configuration file.
98 '';
99 };
100 };
101
102 config = mkIf cfg.enable {
103 assertions =
104 let
105 inRange =
106 v: l: r:
107 v >= l && v <= r;
108 in
109 [
110 {
111 assertion =
112 config.location.provider == "manual"
113 -> inRange config.location.latitude (-90) 90 && inRange config.location.longitude (-180) 180;
114 message = "You must specify a valid latitude and longitude if manually providing location";
115 }
116 ];
117
118 boot.kernelModules = [ "i2c_dev" ];
119 environment.systemPackages = with pkgs; [
120 clight
121 clightd
122 ];
123 services.dbus.packages = with pkgs; [
124 clight
125 clightd
126 ];
127 services.upower.enable = true;
128
129 services.clight.settings =
130 {
131 gamma.temp =
132 with cfg.temperature;
133 mkDefault [
134 day
135 night
136 ];
137 }
138 // (optionalAttrs (config.location.provider == "manual") {
139 daytime.latitude = mkDefault config.location.latitude;
140 daytime.longitude = mkDefault config.location.longitude;
141 });
142
143 services.geoclue2.appConfig.clightc = {
144 isAllowed = true;
145 isSystem = true;
146 };
147
148 systemd.services.clightd = {
149 requires = [ "polkit.service" ];
150 wantedBy = [ "multi-user.target" ];
151
152 description = "Bus service to manage various screen related properties (gamma, dpms, backlight)";
153 serviceConfig = {
154 Type = "dbus";
155 BusName = "org.clightd.clightd";
156 Restart = "on-failure";
157 RestartSec = 5;
158 ExecStart = ''
159 ${pkgs.clightd}/bin/clightd
160 '';
161 };
162 };
163
164 systemd.user.services.clight = {
165 after = [
166 "upower.service"
167 "clightd.service"
168 ];
169 wants = [
170 "upower.service"
171 "clightd.service"
172 ];
173 partOf = [ "graphical-session.target" ];
174 wantedBy = [ "graphical-session.target" ];
175
176 description = "C daemon to adjust screen brightness to match ambient brightness, as computed capturing frames from webcam";
177 serviceConfig = {
178 Restart = "on-failure";
179 RestartSec = 5;
180 ExecStart = ''
181 ${pkgs.clight}/bin/clight --conf-file ${clightConf}
182 '';
183 };
184 };
185 };
186}