1use clap::Parser;
2#[cfg(feature = "debug")]
3use log::{debug, info};
4use serde::{Deserialize, Serialize};
5
6const NOTIFICATION_TIMEOUT: i32 = 5;
7const NOTIFICATION_BODY: &str =
8 "<b>album:</b> {album}<br/><b>Artist:</b> {artist} - {date}";
9const NOTIFICATION_SUMMARY: &str = "{title}";
10const NOTIFICATION_APP_NAME: &str = "C* Music Player";
11const DEFAULT_REMOTE_COMMAND: &str = "cmus-remote";
12const DEFAULT_MAX_DEPTH: u8 = 3;
13const DEFAULT_INTERVAL_TIME: u64 = 1000; const DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY: &str = "<b>{status}</b>";
15const DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY: &str = "{title}";
16const DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT: i32 = 1;
17const DEFAULT_VOLUME_CHANGE_NOTIFICATION_BODY: &str = "Volume changed to {volume}%";
18const DEFAULT_VOLUME_CHANGE_NOTIFICATION_SUMMARY: &str = "{title}";
19const DEFAULT_VOLUME_CHANGE_NOTIFICATION_TIMEOUT: i32 = 1;
20const DEFAULT_SHUFFLE_NOTIFICATION_BODY: &str = "Shuffle mode changed to {shuffle}";
21const DEFAULT_SHUFFLE_NOTIFICATION_SUMMARY: &str = "{title}";
22const DEFAULT_SHUFFLE_NOTIFICATION_TIMEOUT: i32 = 1;
23const DEFAULT_REPEAT_NOTIFICATION_BODY: &str = "Repeat mode changed to {repeat}";
24const DEFAULT_REPEAT_NOTIFICATION_SUMMARY: &str = "{title}";
25const DEFAULT_REPEAT_NOTIFICATION_TIMEOUT: i32 = 1;
26const DEFAULT_AAAMODE_NOTIFICATION_BODY: &str = "AAA mode changed to {aaa_mode}";
27const DEFAULT_AAAMODE_NOTIFICATION_SUMMARY: &str = "{title}";
28const DEFAULT_AAAMODE_NOTIFICATION_TIMEOUT: i32 = 1;
29#[cfg(feature = "lyrics")]
30const DEFAULT_LYRICS_NOTIFICATION_BODY: &str = "{lyrics}";
31#[cfg(feature = "lyrics")]
32const DEFAULT_LYRICS_NOTIFICATION_SUMMARY: &str = "Lyrics";
33
34#[derive(Parser, Serialize, Deserialize)]
35#[cfg_attr(feature = "debug", derive(Debug))]
36#[command(author, about, version, long_about = None)]
37pub struct Settings {
38 #[arg(short, long)]
40 timeout: Option<i32>,
41 #[arg(short, long)]
43 pub persistent: bool,
44 #[arg(short = 'c', long = "cover")]
46 pub show_track_cover: bool,
47 #[arg(short = 'i', long = "icon")]
53 pub notification_static_cover: Option<String>,
54 #[arg(short = 'w', long = "cover-path")]
65 pub cover_path_template: Option<String>,
66 #[cfg(feature = "lyrics")]
67 #[arg(short = 'y', long)]
78 lyrics_path: Option<String>,
79 #[arg(short, long)]
84 depth: Option<u8>,
85 #[arg(short, long)]
87 pub app_name: Option<String>,
88 #[arg(short, long)]
95 summary: Option<String>,
96 #[cfg(feature = "lyrics")]
97 #[arg()]
115 body: Option<String>,
116 #[cfg(not(feature = "lyrics"))]
117 #[arg()]
131 body: Option<String>,
132 #[arg(short = 'b', long = "cmus-remote-bin")]
137 cmus_remote_bin_path: Option<String>,
138 #[arg(short = 'k', long = "cmus-socket")]
140 pub cmus_socket_address: Option<String>,
141 #[arg(short = 'x', long = "socket-password")]
143 pub cmus_socket_password: Option<String>,
144 #[arg(short = 'r', long)]
155 interval: Option<u64>,
156 #[arg(short = 'l', long)]
158 pub link: bool,
159 #[arg(short = 'u', long)]
162 pub force_use_external_cover: bool,
163 #[cfg(feature = "lyrics")]
164 #[arg(short = 'm', long)]
166 pub force_use_external_lyrics: bool,
167 #[arg(short = 'n', long)]
169 pub no_use_external_cover: bool,
170 #[cfg(feature = "lyrics")]
171 #[arg(short = 'o', long)]
173 pub no_use_external_lyrics: bool,
174 #[arg(short = 'g', long)]
176 pub show_player_notifications: bool,
177 #[arg(short = 'B', long)]
182 volume_notification_body: Option<String>,
183 #[arg(short = 'E', long)]
185 volume_notification_summary: Option<String>,
186 #[arg(short = 'T', long)]
188 volume_notification_timeout: Option<i32>,
189 #[arg(short = 'S', long)]
194 shuffle_notification_body: Option<String>,
195 #[arg(short = 'U', long)]
198 shuffle_notification_summary: Option<String>,
199 #[arg(short = 'Y', long)]
201 shuffle_notification_timeout: Option<i32>,
202 #[arg(short = 'R', long)]
207 repeat_notification_body: Option<String>,
208 #[arg(short = 'G', long)]
211 repeat_notification_summary: Option<String>,
212 #[arg(short = 'H', long)]
214 repeat_notification_timeout: Option<i32>,
215 #[arg(short = 'A', long)]
220 aaa_mode_notification_body: Option<String>,
221 #[arg(short = 'D', long)]
224 aaa_mode_notification_summary: Option<String>,
225 #[arg(short = 'F', long)]
227 aaa_mode_notification_timeout: Option<i32>,
228 #[cfg(feature = "lyrics")]
229 #[arg(short = 'L', long)]
234 lyrics_notification_body: Option<String>,
235 #[cfg(feature = "lyrics")]
236 #[arg(short = 'M', long)]
239 lyrics_notification_summary: Option<String>,
240 #[arg(short = 'O', long)]
245 status_notification_body: Option<String>,
246 #[arg(short = 'P', long)]
249 status_notification_summary: Option<String>,
250 #[arg(short = 'Q', long)]
252 status_notification_timeout: Option<i32>,
253 #[cfg(feature = "docs")]
254 #[arg(long, hide = true)]
255 #[serde(skip)]
256 markdown_help: bool,
257 #[arg(long = "config")]
259 #[serde(skip)]
260 config_path: Option<String>,
261}
262
263impl Default for Settings {
264 fn default() -> Self {
265 Self {
266 timeout: Some(NOTIFICATION_TIMEOUT),
267 persistent: false,
268 show_track_cover: true,
269 notification_static_cover: None,
270 cover_path_template: None,
271 #[cfg(feature = "lyrics")]
272 lyrics_path: None,
273 depth: Some(DEFAULT_MAX_DEPTH),
274 app_name: Some(NOTIFICATION_APP_NAME.to_string()),
275 summary: Some(NOTIFICATION_SUMMARY.to_string()),
276 body: Some(NOTIFICATION_BODY.to_string()),
277 cmus_remote_bin_path: Some(DEFAULT_REMOTE_COMMAND.to_string()),
278 cmus_socket_address: None,
279 cmus_socket_password: None,
280 interval: Some(DEFAULT_INTERVAL_TIME),
281 link: false,
282 force_use_external_cover: false,
283 #[cfg(feature = "lyrics")]
284 force_use_external_lyrics: false,
285 no_use_external_cover: false,
286 #[cfg(feature = "lyrics")]
287 no_use_external_lyrics: false,
288 show_player_notifications: false,
289 volume_notification_body: Some(DEFAULT_VOLUME_CHANGE_NOTIFICATION_BODY.to_string()),
290 volume_notification_summary: Some(
291 DEFAULT_VOLUME_CHANGE_NOTIFICATION_SUMMARY.to_string(),
292 ),
293 volume_notification_timeout: Some(DEFAULT_VOLUME_CHANGE_NOTIFICATION_TIMEOUT),
294 shuffle_notification_body: Some(DEFAULT_SHUFFLE_NOTIFICATION_BODY.to_string()),
295 shuffle_notification_summary: Some(DEFAULT_SHUFFLE_NOTIFICATION_SUMMARY.to_string()),
296 shuffle_notification_timeout: Some(DEFAULT_SHUFFLE_NOTIFICATION_TIMEOUT),
297 repeat_notification_body: Some(DEFAULT_REPEAT_NOTIFICATION_BODY.to_string()),
298 repeat_notification_summary: Some(DEFAULT_REPEAT_NOTIFICATION_SUMMARY.to_string()),
299 repeat_notification_timeout: Some(DEFAULT_REPEAT_NOTIFICATION_TIMEOUT),
300 aaa_mode_notification_body: Some(DEFAULT_AAAMODE_NOTIFICATION_BODY.to_string()),
301 aaa_mode_notification_summary: Some(DEFAULT_AAAMODE_NOTIFICATION_SUMMARY.to_string()),
302 aaa_mode_notification_timeout: Some(DEFAULT_AAAMODE_NOTIFICATION_TIMEOUT),
303 #[cfg(feature = "lyrics")]
304 lyrics_notification_body: Some(DEFAULT_LYRICS_NOTIFICATION_BODY.to_string()),
305 #[cfg(feature = "lyrics")]
306 lyrics_notification_summary: Some(DEFAULT_LYRICS_NOTIFICATION_SUMMARY.to_string()),
307 status_notification_body: Some(DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY.to_string()),
308 status_notification_summary: Some(
309 DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY.to_string(),
310 ),
311 status_notification_timeout: Some(DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT),
312 #[cfg(feature = "docs")]
313 markdown_help: false,
314 config_path: None,
315 }
316 }
317}
318
319impl Settings {
320 #[inline(always)]
326 pub fn load_config_and_parse_args() -> Self {
327 #[cfg(feature = "debug")]
328 info!("Parsing args...");
329
330 let args = Settings::parse();
332 #[cfg(feature = "debug")] debug!("Args: {:?}", args);
333
334 #[cfg(feature = "docs")]
335 if args.markdown_help {
336 #[cfg(feature = "debug")]
337 info!("Printing help markdown...");
338 clap_markdown::print_help_markdown::<Settings>();
339 std::process::exit(0);
340 }
341
342 let config_path = args.config_path.unwrap_or(
343 confy::get_configuration_file_path("cmus-notify", "config")
344 .unwrap().to_str().unwrap().to_string());
345 #[cfg(feature = "debug")]
346 {
347 info!("Loading config...");
348 debug!(
349 "Config file path: {:?}",
350 config_path
351 );
352 }
353 let mut cfg: Self = match confy::load_path(config_path) {
355 Ok(cfg) => cfg,
356 Err(err) => {
357 eprintln!("Failed to load config: {}", err);
358 Self::default()
359 }
360 };
361 #[cfg(feature = "debug")]
362 {
363 debug!("Config: {:?}", cfg);
364 info!("Combining config and args...")
365 }
366
367 cfg.timeout = args.timeout.or(cfg.timeout);
369 cfg.persistent = args.persistent || cfg.persistent;
370 cfg.show_track_cover = args.show_track_cover || cfg.show_track_cover;
371 cfg.notification_static_cover = args
372 .notification_static_cover
373 .or(cfg.notification_static_cover);
374 cfg.cover_path_template = args.cover_path_template.or(cfg.cover_path_template);
375 #[cfg(feature = "lyrics")]
376 if args.lyrics_path.is_some() {
377 #[cfg(feature = "debug")]
378 debug!("The user not override the lyrics_path, using the config's lyrics_path. lyrics_path: {:?}", cfg.lyrics_path);
379 cfg.lyrics_path = args.lyrics_path;
380 }
381
382 cfg.depth = args.depth.or(cfg.depth);
383 cfg.app_name = args.app_name.or(cfg.app_name);
384 cfg.summary = args.summary.or(cfg.summary);
385 cfg.body = args.body.or(cfg.body);
386 cfg.cmus_remote_bin_path = args.cmus_remote_bin_path.or(cfg.cmus_remote_bin_path);
387 cfg.cmus_socket_address = args.cmus_socket_address.or(cfg.cmus_socket_address);
388 cfg.cmus_socket_password = args.cmus_socket_password.or(cfg.cmus_socket_password);
389 cfg.interval = args.interval.or(cfg.interval);
390 cfg.link = args.link || cfg.link;
391 cfg.force_use_external_cover =
392 args.force_use_external_cover || cfg.force_use_external_cover;
393 #[cfg(feature = "lyrics")]
394 {
395 cfg.force_use_external_lyrics =
396 args.force_use_external_lyrics || cfg.force_use_external_lyrics;
397 cfg.no_use_external_lyrics = args.no_use_external_lyrics || cfg.no_use_external_lyrics;
398 }
399 cfg.no_use_external_cover = args.no_use_external_cover || cfg.no_use_external_cover;
400 cfg.show_player_notifications =
401 args.show_player_notifications || cfg.show_player_notifications;
402 cfg.volume_notification_body = args
403 .volume_notification_body
404 .or(cfg.volume_notification_body);
405 cfg.volume_notification_summary = args
406 .volume_notification_summary
407 .or(cfg.volume_notification_summary);
408 cfg.volume_notification_timeout = args
409 .volume_notification_timeout
410 .or(cfg.volume_notification_timeout);
411 cfg.shuffle_notification_body = args
412 .shuffle_notification_body
413 .or(cfg.shuffle_notification_body);
414 cfg.shuffle_notification_summary = args
415 .shuffle_notification_summary
416 .or(cfg.shuffle_notification_summary);
417 cfg.shuffle_notification_timeout = args
418 .shuffle_notification_timeout
419 .or(cfg.shuffle_notification_timeout);
420 cfg.repeat_notification_body = args
421 .repeat_notification_body
422 .or(cfg.repeat_notification_body);
423 cfg.repeat_notification_summary = args
424 .repeat_notification_summary
425 .or(cfg.repeat_notification_summary);
426 cfg.repeat_notification_timeout = args
427 .repeat_notification_timeout
428 .or(cfg.repeat_notification_timeout);
429 cfg.aaa_mode_notification_body = args
430 .aaa_mode_notification_body
431 .or(cfg.aaa_mode_notification_body);
432 cfg.aaa_mode_notification_summary = args
433 .aaa_mode_notification_summary
434 .or(cfg.aaa_mode_notification_summary);
435 cfg.aaa_mode_notification_timeout = args
436 .aaa_mode_notification_timeout
437 .or(cfg.aaa_mode_notification_timeout);
438
439 #[cfg(feature = "debug")]
440 info!("The final settings: {:?}", cfg);
441
442 cfg
443 }
444
445 #[inline(always)]
446 pub fn timeout(&self) -> i32 {
447 self.timeout.unwrap_or(NOTIFICATION_TIMEOUT)
448 }
449
450 #[inline(always)]
451 pub fn app_name(&self) -> String {
452 self.app_name
453 .as_ref()
454 .unwrap_or(&NOTIFICATION_APP_NAME.to_string())
455 .to_string()
456 }
457
458 #[inline(always)]
459 pub fn interval(&self) -> u64 {
460 self.interval.unwrap_or(DEFAULT_INTERVAL_TIME)
461 }
462
463 #[inline(always)]
464 pub fn depth(&self) -> u8 {
465 self.depth.unwrap_or(DEFAULT_MAX_DEPTH)
466 }
467
468 #[inline(always)]
469 pub fn remote_bin_path(&self) -> String {
470 self.cmus_remote_bin_path
471 .as_ref()
472 .unwrap_or(&DEFAULT_REMOTE_COMMAND.to_string())
473 .to_string()
474 }
475
476 #[inline(always)]
477 pub fn status_notification_summary(&self) -> String {
478 self.status_notification_summary
479 .as_ref()
480 .unwrap_or(&DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY.to_string())
481 .to_string()
482 }
483
484 #[inline(always)]
485 pub fn status_notification_body(&self) -> String {
486 self.status_notification_body
487 .as_ref()
488 .unwrap_or(&DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY.to_string())
489 .to_string()
490 }
491
492 #[inline(always)]
493 pub fn status_notification_timeout(&self) -> i32 {
494 self.status_notification_timeout
495 .unwrap_or(DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT)
496 }
497
498 #[inline(always)]
499 pub fn summary(&self) -> String {
500 self.summary
501 .as_ref()
502 .unwrap_or(&NOTIFICATION_SUMMARY.to_string())
503 .to_string()
504 }
505
506 #[inline(always)]
507 pub fn body(&self) -> String {
508 self.body
509 .as_ref()
510 .unwrap_or(&NOTIFICATION_BODY.to_string())
511 .to_string()
512 }
513
514 #[inline(always)]
515 pub fn volume_notification_summary(&self) -> String {
516 self.volume_notification_summary
517 .as_ref()
518 .unwrap_or(&DEFAULT_VOLUME_CHANGE_NOTIFICATION_SUMMARY.to_string())
519 .to_string()
520 }
521
522 #[inline(always)]
523 pub fn volume_notification_body(&self) -> String {
524 self.volume_notification_body
525 .as_ref()
526 .unwrap_or(&DEFAULT_VOLUME_CHANGE_NOTIFICATION_BODY.to_string())
527 .to_string()
528 }
529
530 #[inline(always)]
531 pub fn volume_notification_timeout(&self) -> i32 {
532 self.volume_notification_timeout
533 .unwrap_or(DEFAULT_VOLUME_CHANGE_NOTIFICATION_TIMEOUT)
534 }
535
536 #[inline(always)]
537 pub fn shuffle_notification_summary(&self) -> String {
538 self.shuffle_notification_summary
539 .as_ref()
540 .unwrap_or(&DEFAULT_SHUFFLE_NOTIFICATION_SUMMARY.to_string())
541 .to_string()
542 }
543
544 #[inline(always)]
545 pub fn shuffle_notification_body(&self) -> String {
546 self.shuffle_notification_body
547 .as_ref()
548 .unwrap_or(&DEFAULT_SHUFFLE_NOTIFICATION_BODY.to_string())
549 .to_string()
550 }
551
552 #[inline(always)]
553 pub fn shuffle_notification_timeout(&self) -> i32 {
554 self.shuffle_notification_timeout
555 .unwrap_or(DEFAULT_SHUFFLE_NOTIFICATION_TIMEOUT)
556 }
557
558 #[inline(always)]
559 pub fn repeat_notification_summary(&self) -> String {
560 self.repeat_notification_summary
561 .as_ref()
562 .unwrap_or(&DEFAULT_REPEAT_NOTIFICATION_SUMMARY.to_string())
563 .to_string()
564 }
565
566 #[inline(always)]
567 pub fn repeat_notification_body(&self) -> String {
568 self.repeat_notification_body
569 .as_ref()
570 .unwrap_or(&DEFAULT_REPEAT_NOTIFICATION_BODY.to_string())
571 .to_string()
572 }
573
574 #[inline(always)]
575 pub fn repeat_notification_timeout(&self) -> i32 {
576 self.repeat_notification_timeout
577 .unwrap_or(DEFAULT_REPEAT_NOTIFICATION_TIMEOUT)
578 }
579
580 #[inline(always)]
581 pub fn aaa_mode_notification_summary(&self) -> String {
582 self.aaa_mode_notification_summary
583 .as_ref()
584 .unwrap_or(&DEFAULT_AAAMODE_NOTIFICATION_SUMMARY.to_string())
585 .to_string()
586 }
587
588 #[inline(always)]
589 pub fn aaa_mode_notification_body(&self) -> String {
590 self.aaa_mode_notification_body
591 .as_ref()
592 .unwrap_or(&DEFAULT_AAAMODE_NOTIFICATION_BODY.to_string())
593 .to_string()
594 }
595
596 #[inline(always)]
597 pub fn aaa_mode_notification_timeout(&self) -> i32 {
598 self.aaa_mode_notification_timeout
599 .unwrap_or(DEFAULT_AAAMODE_NOTIFICATION_TIMEOUT)
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606
607 #[test]
608 fn test_cli_arguments() {
609 use clap::CommandFactory;
610 Settings::command().debug_assert();
611 }
612}