gonk_core/
toml.rs

1use crate::TOML_DIR;
2use crossterm::event::{KeyCode, KeyModifiers};
3use serde::{Deserialize, Serialize};
4use std::fs;
5use tui::style::Color;
6
7//TODO: test on linux
8#[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        //TODO: this doesn't support triple modfifier combos
30        //plus this is stupid, surely there is a better way
31        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                //TODO: parse and describe error to user?
357                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}