1use crate::{
20 config::Config,
21 types::{
22 Brightness, ColorSettings, Elevation, ElevationRange, Gamma, Location,
23 Period, PeriodInfo, Temperature, Time, TimeOffset, TimeRange,
24 TimeRanges, TransitionScheme,
25 },
26 AdjustmentMethod, DaemonMode, FadeStatus, LocationProvider,
27};
28use anstyle::{AnsiColor, Color, Style};
29use std::fmt::{self, Display, Formatter};
30use tracing::info;
31
32pub const WARN: Style = Style::new()
33 .bold()
34 .fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
35pub const ERR: Style = Style::new()
36 .bold()
37 .fg_color(Some(Color::Ansi(AnsiColor::Red)));
38pub const HEADER: Style = Style::new().bold().underline();
39pub const BODY: Style = Style::new().bold();
40
41impl Display for Temperature {
42 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43 write!(f, "{}", **self)
44 }
45}
46
47struct TemperatureDisplay<'a>(&'a Temperature);
48impl Display for TemperatureDisplay<'_> {
49 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50 write!(f, "{}K", **self.0)
51 }
52}
53
54impl Display for Brightness {
55 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56 write!(f, "{}", **self)
57 }
58}
59
60struct BrightnessDisplay<'a>(&'a Brightness);
61impl Display for BrightnessDisplay<'_> {
62 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
63 write!(f, "{}%", **self.0 as u8 * 100)
64 }
65}
66
67impl Display for Gamma {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 write!(f, "{:.2}:{:.2}:{:.2}", self[0], self[1], self[2])
70 }
71}
72
73struct GammaDisplay<'a>(&'a Gamma);
74impl Display for GammaDisplay<'_> {
75 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
76 write!(f, "{:.2}, {:.2}, {:.2}", self.0[0], self.0[1], self.0[2])
77 }
78}
79
80impl Display for Time {
81 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
82 let Time { hour: h, minute: m } = self;
83 write!(f, "{h:02}:{m:02}")
84 }
85}
86
87impl Display for TimeOffset {
88 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
89 Display::fmt(&Time::from(*self), f)
90 }
91}
92
93impl Display for TimeRange {
94 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
95 let Self { start, end } = self;
96 write!(f, "from {start} to {end}")
97 }
98}
99
100impl Display for Elevation {
101 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102 write!(f, "{:.2}°", **self)
104 }
105}
106
107impl Display for Location {
108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 let a = *self.lat;
110 let b = *self.lon;
111 let ns = if a >= 0.0 { "N" } else { "S" };
112 let ew = if b >= 0.0 { "E" } else { "W" };
113 let a = a.abs();
114 let a1 = a as u8;
115 let a2 = (a.fract() * 100.0) as u8;
116 let a3 = ((a * 100.0).fract() * 100.0) as u8;
117 let b = b.abs();
118 let b1 = b as u8;
119 let b2 = (b.fract() * 100.0) as u8;
120 let b3 = ((b * 100.0).fract() * 100.0) as u8;
121 write!(f, "{a1}°{a2}′{a3}″{ns}, {b1}°{b2}′{b3}″{ew}")
122 }
123}
124
125struct ColorSettingsDisplay<'a> {
128 temp: TemperatureDisplay<'a>,
129 gamma: GammaDisplay<'a>,
130 brght: BrightnessDisplay<'a>,
131}
132
133impl<'a> From<&'a ColorSettings> for ColorSettingsDisplay<'a> {
134 fn from(cs: &'a ColorSettings) -> Self {
135 let ColorSettings { temp, gamma, brght } = cs;
136 let temp = TemperatureDisplay(temp);
137 let gamma = GammaDisplay(gamma);
138 let brght = BrightnessDisplay(brght);
139 Self { temp, gamma, brght }
140 }
141}
142
143impl Display for ColorSettings {
144 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
145 let ColorSettingsDisplay { temp, gamma, brght } = self.into();
146 write!(
147 f,
148 " {BODY}Temperature{BODY:#}: {temp}K
149 {BODY}Brightness{BODY:#}: {brght}%
150 {BODY}Gamma{BODY:#}: {gamma}"
151 )
152 }
153}
154
155impl Display for Period {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 match self {
158 Period::Night => write!(f, " {BODY}Period{BODY:#}: night"),
159 Period::Daytime => write!(f, " {BODY}Period{BODY:#}: daytime"),
160 Period::Transition { progress } => {
161 write!(
162 f,
163 " {BODY}Period{BODY:#}: transition ({progress}% day)"
164 )
165 }
166 }
167 }
168}
169
170impl Display for PeriodInfo {
171 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
172 match self {
173 PeriodInfo::Time => Ok(()),
174 PeriodInfo::Elevation { elev, loc } => {
175 write!(
176 f,
177 " {BODY}Solar elevation{BODY:#}: {elev}
178 {BODY}Location{BODY:#}: {loc}"
179 )
180 }
181 }
182 }
183}
184
185impl Display for AdjustmentMethod {
186 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187 let s = match self {
188 AdjustmentMethod::Dummy(_) => "dummy",
189 #[cfg(unix_without_macos)]
190 AdjustmentMethod::Randr(_) => "randr",
191 #[cfg(unix_without_macos)]
192 AdjustmentMethod::Drm(_) => "drm",
193 #[cfg(unix_without_macos)]
194 AdjustmentMethod::Vidmode(_) => "vidmode",
195 #[cfg(windows)]
196 AdjustmentMethod::Win32Gdi(_) => "win32gdi",
197 };
198 write!(f, "{s}")
199 }
200}
201
202impl Display for LocationProvider {
203 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204 let s = match self {
205 LocationProvider::Manual(_) => "manual",
206 LocationProvider::Geoclue2(_) => "geoclue2",
207 };
208 write!(f, "{s}")
209 }
210}
211
212impl Display for Config {
213 #[allow(clippy::too_many_lines)]
214 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
215 let Config {
216 day,
217 night,
218 scheme,
219 location,
220 method,
221 reset_ramps,
222 disable_fade,
223 sleep_duration_short,
224 sleep_duration,
225 mode: _,
226 time: _,
227 } = self;
228
229 writeln!(f, "{BODY}Adjustment method{BODY:#}: {method}")?;
230 writeln!(f, "{BODY}Location provider{BODY:#}: {location}")?;
231 writeln!(f, "{BODY}Reset ramps{BODY:#}: {reset_ramps}")?;
232 writeln!(f, "{BODY}Disable fade{BODY:#}: {disable_fade}")?;
233 let s = sleep_duration.as_millis();
234 writeln!(f, "{BODY}Sleep duration{BODY:#}: {s}ms")?;
235 let s = sleep_duration_short.as_millis();
236 writeln!(f, "{BODY}Sleep duration short{BODY:#}: {s}ms")?;
237
238 writeln!(f, "{HEADER}Daytime{HEADER:#}:")?;
239 match scheme {
240 TransitionScheme::Time(TimeRanges {
241 dawn: TimeRange { end, .. },
242 dusk: TimeRange { start, .. },
243 }) => {
244 writeln!(f, " {BODY}Time{BODY:#}: from {end} to {start}")?;
245 }
246 TransitionScheme::Elev(ElevationRange { high, .. }) => {
247 writeln!(
248 f,
249 " {BODY}Solar elevation{BODY:#}: above {high}"
250 )?;
251 }
252 }
253 writeln!(f, "{day}")?;
254
255 writeln!(f, "{HEADER}Night{HEADER:#}:")?;
256 match scheme {
257 TransitionScheme::Time(TimeRanges {
258 dawn: TimeRange { start, .. },
259 dusk: TimeRange { end, .. },
260 }) => {
261 writeln!(f, " {BODY}Time{BODY:#}: from {end} to {start}")?;
262 }
263 TransitionScheme::Elev(ElevationRange { low, .. }) => {
264 writeln!(f, " {BODY}Solar elevation{BODY:#}: below {low}")?;
265 }
266 }
267 write!(f, "{night}")?;
268 Ok(())
269 }
270}
271
272impl DaemonMode<'_, '_> {
273 #[allow(clippy::too_many_lines)]
274 pub fn log(&self) {
275 if Some(&self.period) != self.prev_period.as_ref() {
276 info!("{}", self.period);
277 }
278 match (&self.info, &self.prev_info) {
279 (
280 PeriodInfo::Elevation { elev: e1, .. },
281 Some(PeriodInfo::Elevation { elev: e2, .. }),
282 ) if e1 != e2 => {
283 info!(" {BODY}Solar elevation{BODY:#}: {e1}");
284 }
285 (
286 PeriodInfo::Elevation { loc: l1, .. },
287 Some(PeriodInfo::Elevation { loc: l2, .. }),
288 ) if l1 != l2 => {
289 info!(" {BODY}Location{BODY:#}: {l1}");
290 }
291 (PeriodInfo::Elevation { .. }, None) => {
292 info!("{}", self.info);
293 }
294 (
295 i1 @ PeriodInfo::Elevation { .. },
296 Some(i2 @ PeriodInfo::Elevation { .. }),
297 ) if i1 != i2 => {
298 info!("{}", self.info);
299 }
300 _ => {}
301 }
302
303 let ColorSettingsDisplay { temp, gamma, brght } =
304 (&self.interp).into();
305 if self.fade == FadeStatus::Completed || self.prev_interp.is_none() {
306 if Some(temp.0) != self.prev_interp.as_ref().map(|c| &c.temp) {
307 info!(" {BODY}Temperature{BODY:#}: {temp}K");
308 }
309 if Some(gamma.0) != self.prev_interp.as_ref().map(|c| &c.gamma) {
310 info!(" {BODY}Gamma{BODY:#}: {gamma}");
311 }
312 if Some(brght.0) != self.prev_interp.as_ref().map(|c| &c.brght) {
313 info!(" {BODY}Brightness{BODY:#}: {brght}%");
314 }
315 } else if Some(temp.0) != self.prev_interp.as_ref().map(|c| &c.temp) {
316 info!(" {BODY}Temperature{BODY:#}: {temp}K");
317 }
318 }
319}