1use crate::TOML_DIR;
2use crossterm::event::{KeyCode, KeyModifiers};
3use serde::{Deserialize, Serialize};
4use std::fs;
5use tui::style::Color;
6
7#[cfg(windows)]
9use global_hotkeys::{keys, modifiers};
10
11#[allow(clippy::upper_case_acronyms)]
12#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
13pub enum Modifier {
14 CONTROL,
15 SHIFT,
16 ALT,
17}
18
19impl Modifier {
20 #[cfg(windows)]
21 pub fn as_u32(&self) -> u32 {
22 match self {
23 Modifier::CONTROL => modifiers::CONTROL,
24 Modifier::SHIFT => modifiers::SHIFT,
25 Modifier::ALT => modifiers::ALT,
26 }
27 }
28 pub fn from_bitflags(m: KeyModifiers) -> Option<Vec<Self>> {
29 match m.bits() {
32 0b0000_0001 => Some(vec![Modifier::SHIFT]),
33 0b0000_0100 => Some(vec![Modifier::ALT]),
34 0b0000_0010 => Some(vec![Modifier::CONTROL]),
35 3 => Some(vec![Modifier::CONTROL, Modifier::SHIFT]),
36 5 => Some(vec![Modifier::ALT, Modifier::SHIFT]),
37 6 => Some(vec![Modifier::CONTROL, Modifier::ALT]),
38 _ => None,
39 }
40 }
41}
42
43impl From<&Modifier> for KeyModifiers {
44 fn from(m: &Modifier) -> Self {
45 match m {
46 Modifier::CONTROL => KeyModifiers::CONTROL,
47 Modifier::SHIFT => KeyModifiers::SHIFT,
48 Modifier::ALT => KeyModifiers::ALT,
49 }
50 }
51}
52
53#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
54pub struct Key(pub String);
55
56impl From<&str> for Key {
57 fn from(key: &str) -> Self {
58 Self(key.to_string())
59 }
60}
61
62impl From<KeyCode> for Key {
63 fn from(item: KeyCode) -> Self {
64 match item {
65 KeyCode::Char(' ') => Key::from("SPACE"),
66 KeyCode::Char(c) => Key(c.to_string().to_ascii_uppercase()),
67 KeyCode::Backspace => Key::from("BACKSPACE"),
68 KeyCode::Enter => Key::from("ENTER"),
69 KeyCode::Left => Key::from("LEFT"),
70 KeyCode::Right => Key::from("RIGHT"),
71 KeyCode::Up => Key::from("UP"),
72 KeyCode::Down => Key::from("DOWN"),
73 KeyCode::Home => Key::from("HOME"),
74 KeyCode::End => Key::from("END"),
75 KeyCode::PageUp => Key::from("PAGEUP"),
76 KeyCode::PageDown => Key::from("PAGEDOWN"),
77 KeyCode::Tab => Key::from("TAB"),
78 KeyCode::BackTab => Key::from("BACKTAB"),
79 KeyCode::Delete => Key::from("DELETE"),
80 KeyCode::Insert => Key::from("INSERT"),
81 KeyCode::F(num) => Key(format!("F{num}")),
82 KeyCode::Null => Key::from("NULL"),
83 KeyCode::Esc => Key::from("ESCAPE"),
84 }
85 }
86}
87
88#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
89pub struct Bind {
90 pub key: Key,
91 pub modifiers: Option<Vec<Modifier>>,
92}
93
94impl Bind {
95 pub fn new(key: &str) -> Self {
96 Self {
97 key: Key::from(key),
98 modifiers: None,
99 }
100 }
101
102 #[cfg(windows)]
103 pub fn modifiers(&self) -> u32 {
104 if let Some(m) = &self.modifiers {
105 m.iter().map(Modifier::as_u32).sum()
106 } else {
107 0
108 }
109 }
110
111 #[cfg(windows)]
112 pub fn key(&self) -> u32 {
113 match self.key.0.as_str() {
114 "SPACE" => keys::SPACEBAR,
115 "BACKSPACE" => keys::BACKSPACE,
116 "ENTER" => keys::ENTER,
117 "UP" => keys::ARROW_UP,
118 "DOWN" => keys::ARROW_DOWN,
119 "LEFT" => keys::ARROW_LEFT,
120 "RIGHT" => keys::ARROW_RIGHT,
121 "HOME" => keys::HOME,
122 "END" => keys::END,
123 "PAGEUP" => keys::PAGE_UP,
124 "PAGEDOWN" => keys::PAGE_DOWN,
125 "TAB" => keys::TAB,
126 "DELETE" => keys::DELETE,
127 "INSERT" => keys::INSERT,
128 "ESCAPE" => keys::ESCAPE,
129 "CAPSLOCK" => keys::CAPS_LOCK,
130 key => {
131 if let Some(char) = key.chars().next() {
132 char as u32
133 } else {
134 0
135 }
136 }
137 }
138 }
139}
140
141#[derive(Serialize, Deserialize, Clone)]
142pub struct Hotkey {
143 pub up: Vec<Bind>,
144 pub down: Vec<Bind>,
145 pub left: Vec<Bind>,
146 pub right: Vec<Bind>,
147 pub play_pause: Vec<Bind>,
148 pub volume_up: Vec<Bind>,
149 pub volume_down: Vec<Bind>,
150 pub next: Vec<Bind>,
151 pub previous: Vec<Bind>,
152 pub seek_forward: Vec<Bind>,
153 pub seek_backward: Vec<Bind>,
154 pub clear: Vec<Bind>,
155 pub clear_except_playing: Vec<Bind>,
156 pub delete: Vec<Bind>,
157 pub search: Vec<Bind>,
158 pub options: Vec<Bind>,
159 pub random: Vec<Bind>,
160 pub change_mode: Vec<Bind>,
161 pub refresh_database: Vec<Bind>,
162 pub quit: Vec<Bind>,
163}
164
165#[derive(Serialize, Deserialize, Clone)]
166pub struct GlobalHotkey {
167 pub play_pause: Bind,
168 pub volume_up: Bind,
169 pub volume_down: Bind,
170 pub next: Bind,
171 pub previous: Bind,
172}
173
174#[derive(Serialize, Deserialize, Clone)]
175pub struct Config {
176 pub paths: Vec<String>,
177 pub output_device: String,
178 pub volume: u16,
179}
180
181#[derive(Serialize, Deserialize, Clone)]
182pub struct Colors {
183 pub track: Color,
184 pub title: Color,
185 pub album: Color,
186 pub artist: Color,
187
188 pub seeker: Color,
189}
190
191#[derive(Serialize, Deserialize, Clone)]
192pub struct Toml {
193 pub config: Config,
194 pub colors: Colors,
195 pub hotkey: Hotkey,
196 pub global_hotkey: GlobalHotkey,
197}
198
199impl Toml {
200 pub fn new() -> Self {
201 let file = if TOML_DIR.exists() {
202 fs::read_to_string(TOML_DIR.as_path()).unwrap()
203 } else {
204 let toml = Toml {
205 config: Config {
206 paths: Vec::new(),
207 output_device: String::new(),
208 volume: 15,
209 },
210 colors: Colors {
211 track: Color::Green,
212 title: Color::Cyan,
213 album: Color::Magenta,
214 artist: Color::Blue,
215 seeker: Color::White,
216 },
217 global_hotkey: GlobalHotkey {
218 play_pause: Bind {
219 key: Key::from("CAPSLOCK"),
220 modifiers: Some(vec![Modifier::SHIFT]),
221 },
222 volume_up: Bind {
223 key: Key::from("2"),
224 modifiers: Some(vec![Modifier::SHIFT, Modifier::ALT]),
225 },
226 volume_down: Bind {
227 key: Key::from("1"),
228 modifiers: Some(vec![Modifier::SHIFT, Modifier::ALT]),
229 },
230 next: Bind {
231 key: Key::from("W"),
232 modifiers: Some(vec![Modifier::SHIFT, Modifier::ALT]),
233 },
234 previous: Bind {
235 key: Key::from("Q"),
236 modifiers: Some(vec![Modifier::SHIFT, Modifier::ALT]),
237 },
238 },
239 hotkey: Hotkey {
240 up: vec![
241 Bind {
242 key: Key::from("K"),
243 modifiers: None,
244 },
245 Bind {
246 key: Key::from("UP"),
247 modifiers: None,
248 },
249 ],
250 down: vec![
251 Bind {
252 key: Key::from("J"),
253 modifiers: None,
254 },
255 Bind {
256 key: Key::from("DOWN"),
257 modifiers: None,
258 },
259 ],
260 left: vec![
261 Bind {
262 key: Key::from("H"),
263 modifiers: None,
264 },
265 Bind {
266 key: Key::from("LEFT"),
267 modifiers: None,
268 },
269 ],
270 right: vec![
271 Bind {
272 key: Key::from("L"),
273 modifiers: None,
274 },
275 Bind {
276 key: Key::from("RIGHT"),
277 modifiers: None,
278 },
279 ],
280 play_pause: vec![Bind {
281 key: Key::from("SPACE"),
282 modifiers: None,
283 }],
284 volume_up: vec![Bind {
285 key: Key::from("W"),
286 modifiers: None,
287 }],
288 volume_down: vec![Bind {
289 key: Key::from("S"),
290 modifiers: None,
291 }],
292 seek_forward: vec![Bind {
293 key: Key::from("E"),
294 modifiers: None,
295 }],
296 seek_backward: vec![Bind {
297 key: Key::from("Q"),
298 modifiers: None,
299 }],
300 next: vec![Bind {
301 key: Key::from("D"),
302 modifiers: None,
303 }],
304 previous: vec![Bind {
305 key: Key::from("A"),
306 modifiers: None,
307 }],
308 clear: vec![Bind {
309 key: Key::from("C"),
310 modifiers: None,
311 }],
312 clear_except_playing: vec![Bind {
313 key: Key::from("C"),
314 modifiers: Some(vec![Modifier::SHIFT]),
315 }],
316 delete: vec![Bind {
317 key: Key::from("X"),
318 modifiers: None,
319 }],
320 search: vec![Bind {
321 key: Key::from("/"),
322 modifiers: None,
323 }],
324 options: vec![Bind {
325 key: Key::from("."),
326 modifiers: None,
327 }],
328 random: vec![Bind {
329 key: Key::from("R"),
330 modifiers: None,
331 }],
332 change_mode: vec![Bind {
333 key: Key::from("TAB"),
334 modifiers: None,
335 }],
336 refresh_database: vec![Bind {
337 key: Key::from("U"),
338 modifiers: None,
339 }],
340 quit: vec![Bind {
341 key: Key::from("C"),
342 modifiers: Some(vec![Modifier::CONTROL]),
343 }],
344 },
345 };
346
347 match toml::to_string_pretty(&toml) {
348 Ok(toml) => toml,
349 Err(err) => panic!("{}", &err),
350 }
351 };
352
353 match toml::from_str(&file) {
354 Ok(toml) => toml,
355 Err(err) => {
356 panic!("{:#?}", &err);
358 }
359 }
360 }
361 pub fn volume(&self) -> u16 {
362 self.config.volume
363 }
364 pub fn paths(&self) -> &[String] {
365 &self.config.paths
366 }
367 pub fn output_device(&self) -> &String {
368 &self.config.output_device
369 }
370 pub fn add_path(&mut self, path: String) {
371 if !self.config.paths.contains(&path) {
372 self.config.paths.push(path);
373 self.write();
374 }
375 }
376 pub fn set_volume(&mut self, vol: u16) {
377 self.config.volume = vol;
378 self.write();
379 }
380 pub fn set_output_device(&mut self, device: String) {
381 self.config.output_device = device;
382 self.write();
383 }
384 pub fn write(&self) {
385 let toml = toml::to_string(&self).expect("Failed to write toml file.");
386 fs::write(TOML_DIR.as_path(), toml).expect("Could not write toml flie.");
387 }
388 pub fn reset(&mut self) {
389 self.config.paths = Vec::new();
390 self.write();
391 }
392}
393
394impl Default for Toml {
395 fn default() -> Self {
396 Toml::new()
397 }
398}