termusiclib/config/v2/tui/
mod.rs1use std::{collections::HashSet, path::Path};
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5
6use super::server::ComSettings;
7
8pub mod config_extra;
9pub mod keys;
10pub mod theme;
11
12#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
13#[serde(default)] #[allow(clippy::module_name_repetitions)]
15pub struct TuiSettings {
16 pub com: MaybeComSettings,
17 #[serde(skip)]
19 pub com_resolved: Option<ComSettings>,
20 pub behavior: BehaviorSettings,
21 pub coverart: CoverArt,
22 #[serde(flatten)]
23 pub theme: theme::ThemeWrap,
24 pub keys: keys::Keys,
25 pub ytdlp: Ytdlp,
26}
27
28impl TuiSettings {
29 pub fn resolve_com(&mut self, tui_path: &Path) -> Result<()> {
33 if self.com_resolved.is_some() {
34 return Ok(());
35 }
36
37 match self.com {
38 MaybeComSettings::ComSettings(ref v) => {
39 self.com_resolved = Some(v.clone());
41 return Ok(());
42 }
43 MaybeComSettings::Same => (),
44 }
45
46 let server_path = tui_path
47 .parent()
48 .context("tui_path should have a parent directory")?
49 .join(super::server::config_extra::FILE_NAME);
50
51 let server_settings =
52 super::server::config_extra::ServerConfigVersionedDefaulted::from_file(server_path)
53 .context("parsing server config")?;
54 self.com_resolved = Some(server_settings.into_settings().com);
55
56 Ok(())
57 }
58
59 #[must_use]
61 pub fn get_com(&self) -> Option<&ComSettings> {
62 self.com_resolved.as_ref()
63 }
64}
65
66#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
67pub struct BehaviorSettings {
68 pub quit_server_on_exit: bool,
70 pub confirm_quit: bool,
72}
73
74impl Default for BehaviorSettings {
75 fn default() -> Self {
76 Self {
77 quit_server_on_exit: true,
78 confirm_quit: true,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
84#[serde(rename_all = "lowercase")]
85pub enum MaybeComSettings {
86 ComSettings(ComSettings),
87 #[default]
89 Same,
90}
91
92#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
93#[serde(default)] #[derive(Default)]
95pub struct CoverArt {
96 pub align: Alignment,
99 pub size_scale: i8,
101 pub hidden: bool,
103
104 pub protocols: CoverArtProtocolsSet,
108}
109
110#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
111#[serde(transparent)]
112pub struct CoverArtProtocolsSet(HashSet<CoverArtProtocol>);
113
114impl Default for CoverArtProtocolsSet {
115 fn default() -> Self {
116 Self(HashSet::from(*PROTOCOLS_DEFAULT))
117 }
118}
119
120impl CoverArtProtocolsSet {
121 #[inline]
123 #[must_use]
124 pub fn includes_protocol(&self, protocol: CoverArtProtocol) -> bool {
125 self.0.contains(&protocol)
126 }
127}
128
129pub const PROTOCOLS_DEFAULT: &[CoverArtProtocol; 4] = &[
131 CoverArtProtocol::Kitty,
132 CoverArtProtocol::Iterm2,
133 CoverArtProtocol::Sixel,
134 CoverArtProtocol::Ueberzug,
135];
136
137#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
141pub enum CoverArtProtocol {
142 #[serde(rename = "sixel")]
143 Sixel,
144 #[serde(rename = "iterm2", alias = "iterm")]
145 Iterm2,
146 #[serde(rename = "kitty")]
147 Kitty,
148 #[serde(rename = "ueberzug")]
149 Ueberzug,
150}
151
152#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq)]
153pub enum Alignment {
154 #[serde(rename = "top right")]
155 TopRight,
156 #[serde(rename = "top left")]
157 TopLeft,
158 #[serde(rename = "bottom right")]
159 #[default]
160 BottomRight,
161 #[serde(rename = "bottom left")]
162 BottomLeft,
163}
164
165#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Default)]
166#[serde(default)] pub struct Ytdlp {
168 pub extra_args: String,
170}
171
172mod v1_interop {
173 use super::{Alignment, BehaviorSettings, CoverArt, MaybeComSettings, TuiSettings, Ytdlp};
174 use crate::config::{v1, v2::tui::CoverArtProtocolsSet};
175
176 impl From<v1::Alignment> for Alignment {
177 fn from(value: v1::Alignment) -> Self {
178 match value {
179 v1::Alignment::BottomRight => Self::BottomRight,
180 v1::Alignment::BottomLeft => Self::BottomLeft,
181 v1::Alignment::TopRight => Self::TopRight,
182 v1::Alignment::TopLeft => Self::TopLeft,
183 }
184 }
185 }
186
187 #[allow(clippy::cast_possible_truncation)] impl From<v1::Xywh> for CoverArt {
189 fn from(value: v1::Xywh) -> Self {
190 Self {
191 align: value.align.into(),
192 size_scale: value.width_between_1_100.clamp(0, i8::MAX as u32) as i8,
194 hidden: Self::default().hidden,
195 protocols: CoverArtProtocolsSet::default(),
196 }
197 }
198 }
199
200 impl From<v1::Settings> for TuiSettings {
201 fn from(value: v1::Settings) -> Self {
202 let theme = (&value).into();
203 Self {
204 com: MaybeComSettings::Same,
206 com_resolved: None,
207 behavior: BehaviorSettings {
208 quit_server_on_exit: value.kill_daemon_when_quit,
209 confirm_quit: value.enable_exit_confirmation,
210 },
211 coverart: value.album_photo_xywh.into(),
212 theme,
213 keys: value.keys.into(),
214 ytdlp: Ytdlp::default(),
215 }
216 }
217 }
218
219 #[cfg(test)]
220 mod tests {
221 use super::*;
222
223 #[test]
224 fn should_convert_default_without_error() {
225 let converted: TuiSettings = v1::Settings::default().into();
226
227 assert_eq!(converted.com, MaybeComSettings::Same);
228 assert_eq!(
229 converted.behavior,
230 BehaviorSettings {
231 quit_server_on_exit: true,
232 confirm_quit: true
233 }
234 );
235
236 assert_eq!(
237 converted.coverart,
238 CoverArt {
239 align: Alignment::BottomRight,
240 size_scale: 20,
241 hidden: false,
242 protocols: CoverArtProtocolsSet::default()
243 }
244 );
245
246 }
250 }
251}