cmus_notify/
settings.rs

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; // 1000 ms
14const 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    /// The notification timeout, in seconds
39    #[arg(short, long)]
40    timeout: Option<i32>,
41    /// Make the notification persistent, i.e. not disappear after a timeout (you can dismiss it manually)
42    #[arg(short, long)]
43    pub persistent: bool,
44    /// Show the track cover in the notification, if available
45    #[arg(short = 'c', long = "cover")]
46    pub show_track_cover: bool,
47    /// The static icon to use for the notification, it not effective if the track cover is shown,
48    /// but if the cover is not available or you disabled it, this icon will be used.
49    ///
50    /// you can give it the full path to an image file or a name of an icon from the current icon theme
51    /// (e.g. "audio-x-generic" or "spotify-client")
52    #[arg(short = 'i', long = "icon")]
53    pub notification_static_cover: Option<String>,
54    /// The path to look for the cover image, if not given, the cover will be searched in the track's directory
55    /// for an image file with the name "cover".
56    ///
57    /// You can use the placeholder "{artist}" and "{album}" and "{title}" and "{track_number}" and
58    /// "{disc_number}" and "{year}" and "{genre}" in the path, they will be replaced with the corresponding metadata.
59    /// but if the metadata is not available, the placeholder will be replaced with an empty string.
60    /// And you can use the simple glob pattern `*` to match any character.
61    /// e.g. "covers/{artist}/{album}/cover.*", "covers/{artist}/{album}/*",
62    ///
63    /// If you not specify the full path, the cover will be started from the track's directory.
64    #[arg(short = 'w', long = "cover-path")]
65    pub cover_path_template: Option<String>,
66    #[cfg(feature = "lyrics")]
67    /// The lyrics file path, if not given, the lyrics will be searched in the track's directory
68    /// for a text file with the name "lyrics", or with the same name as the track.
69    ///
70    /// You can use the placeholder "{artist}" and "{album}" and "{title}" and "{track_number}" and
71    /// "{disc_number}" and "{year}" and "{genre}" in the path, they will be replaced with the corresponding metadata.
72    /// but if the metadata is not available, the placeholder will be replaced with an empty string.
73    /// And you can use the simple glob pattern `*` to match any character.
74    /// e.g. "lyrics/{artist}/{album}/{title}.lrc", "lyrics/{artist}/{album}/*",
75    ///
76    /// If you not specify the full path, the lyrics will be started from the track's directory.
77    #[arg(short = 'y', long)]
78    lyrics_path: Option<String>,
79    /// The maximum path depth to search for the cover and lyrics files,
80    /// if the files are not found in the track's directory, or the directory specified by the `--cover-path`
81    /// or `--lyrics-path`* options, the program will search in the parent directory,
82    /// and so on, until the maximum depth is reached.
83    #[arg(short, long)]
84    depth: Option<u8>,
85    /// The name of the app to use for the notification.
86    #[arg(short, long)]
87    pub app_name: Option<String>,
88    /// The summary of the notification.
89    ///
90    /// you can use the placeholder "{artist}" and "{album}" and "{title}" and "{track_number}" and
91    /// "{disc_number}" and "{year}" and "{genre}" in the summary, they will be replaced with the corresponding metadata.
92    /// but if the metadata is not available, the placeholder will be replaced with an empty string.
93    /// e.g. "{artist} - {title}"
94    #[arg(short, long)]
95    summary: Option<String>,
96    #[cfg(feature = "lyrics")]
97    /// The body of the notification.
98    ///
99    /// you can use the placeholder "{artist}" and "{album}" and "{title}" and "{track_number}" and
100    /// "{disc_number}" and "{year}" and "{genre}" in the body, they will be replaced with the corresponding metadata.
101    /// but if the metadata is not available, the placeholder will be replaced with an empty string.
102    ///
103    /// And you can use the placeholder "{lyrics}" to show the lyrics of the track, if available.
104    /// But if you use this placeholder, the notification will be persistent, and you need to dismiss it manually tow times.
105    ///
106    /// Also you can use the placeholder "{progress}" to show the progress of the track, in the format "00:00 / 00:00".
107    ///
108    /// Also you can use the placeholder "{progress_bar}" to show the progress bar of the track.
109    ///
110    /// Like the "{lyrics}" placeholder, if you use the "{progress}" or "{progress_bar}" placeholder,
111    /// the notification will be persistent, and you need to dismiss it manually tow times.
112    ///
113    /// Also you can use the simple html markup, if your notification server supports it.
114    #[arg()]
115    body: Option<String>,
116    #[cfg(not(feature = "lyrics"))]
117    /// The body of the notification.
118    ///
119    /// you can use the placeholder "{artist}" and "{album}" and "{title}" and "{track_number}" and
120    /// "{disc_number}" and "{year}" and "{genre}" in the body, they will be replaced with the corresponding metadata.
121    /// but if the metadata is not available, the placeholder will be replaced with an empty string.
122    ///
123    /// And you can use the placeholder "{progress}" to show the progress of the track, in the format "00:00 / 00:00".
124    /// Also you can use the placeholder "{progress_bar}" to show the progress bar of the track.
125    ///
126    /// But if you use the "{progress}" or "{progress_bar}" placeholder,
127    /// the notification will be persistent, and you need to dismiss it manually tow times.
128    ///
129    /// Also you can use the simple html markup, if your notification server supports it.
130    #[arg()]
131    body: Option<String>,
132    /// The cmus-remote binary path, if not given, the program will search for it in the PATH environment variable.
133    ///
134    /// if you're using a custom package format like flatpak, or snap, you can give it the full run command (without any arguments),
135    /// e.g. "flatpak run io.github.cmus.cmus", "snap run cmus"
136    #[arg(short = 'b', long = "cmus-remote-bin")]
137    cmus_remote_bin_path: Option<String>,
138    /// The cmus socket address, if not given, the program will use the default socket address, which is "$XDG_RUNTIME_DIR/cmus-socket".
139    #[arg(short = 'k', long = "cmus-socket")]
140    pub cmus_socket_address: Option<String>,
141    /// The cmus socket password, if any.
142    #[arg(short = 'x', long = "socket-password")]
143    pub cmus_socket_password: Option<String>,
144    /// The interval to request the cmus status, in milliseconds.
145    ///
146    /// if you set it to 0, the program will only request the cmus status once and wait for the track duration time to make another request.
147    ///  e.g. if the track duration is 3 minutes, the program will request the cmus status once, and wait for 3 minutes to make another request,
148    /// and if the track changes to another track with 5 minutes duration, the program will request the cmus status once, and wait for 5 minutes to make another request at so on.
149    ///
150    /// this is useful if you have a potato computer, and you don't want to waste your CPU and battery,
151    /// but it will make the notification a little bit stupid, if you change the track manually, the notification will not update until the track duration time is reached.
152    ///
153    /// but I recommend 1s, it's not too fast, and not too slow, and it will not waste your CPU and battery.
154    #[arg(short = 'r', long)]
155    interval: Option<u64>,
156    /// Link the program with cmus, if the cmus are not running, the program will exit.
157    #[arg(short = 'l', long)]
158    pub link: bool,
159    /// Force the program to use the external cover file, if available, and not even try to get the cover from the track's metadata.
160    /// this is useful if you have a cover file with a better quality than the cover in the track's metadata.
161    #[arg(short = 'u', long)]
162    pub force_use_external_cover: bool,
163    #[cfg(feature = "lyrics")]
164    /// Fotrce the program to use the external lyrics file, if available, and not even try to get the lyrics from the track's metadata.
165    #[arg(short = 'm', long)]
166    pub force_use_external_lyrics: bool,
167    /// No use the external cover file, even if it's available and the track's metadata doesn't have a cover.
168    #[arg(short = 'n', long)]
169    pub no_use_external_cover: bool,
170    #[cfg(feature = "lyrics")]
171    /// No use the external lyrics file, even if it's available and the track's metadata doesn't have a lyrics.
172    #[arg(short = 'o', long)]
173    pub no_use_external_lyrics: bool,
174    /// Show the player notifications, like if you change the shuffle mode, or the repeat mode, or the volume.
175    #[arg(short = 'g', long)]
176    pub show_player_notifications: bool,
177    /// The volume change notification body.
178    /// you can use the placeholders like "{volume}" in the body, it will be replaced with the shuffle mode.
179    ///
180    /// If you leave it empty, the notification will not be shown.
181    #[arg(short = 'B', long)]
182    volume_notification_body: Option<String>,
183    /// The volume change notification summary.
184    #[arg(short = 'E', long)]
185    volume_notification_summary: Option<String>,
186    /// The time out of the volume change notification, in seconds.
187    #[arg(short = 'T', long)]
188    volume_notification_timeout: Option<i32>,
189    /// The shuffle mode change notification body.
190    /// you can use the placeholders like "{shuffle}" in the body, it will be replaced with the shuffle mode.
191    ///
192    /// If you leave it empty, the notification will not be shown.
193    #[arg(short = 'S', long)]
194    shuffle_notification_body: Option<String>,
195    /// The shuffle mode change notification summary.
196    /// you can use the placeholders like "{shuffle}" in the summary, it will be replaced with the shuffle mode.
197    #[arg(short = 'U', long)]
198    shuffle_notification_summary: Option<String>,
199    /// The time out of the shuffle mode change notification, in seconds.
200    #[arg(short = 'Y', long)]
201    shuffle_notification_timeout: Option<i32>,
202    /// The repeat mode change notification body.
203    /// you can use the placeholders like "{repeat}" in the body, it will be replaced with the repeat mode.
204    ///
205    /// If you leave it empty, the notification will not be shown.
206    #[arg(short = 'R', long)]
207    repeat_notification_body: Option<String>,
208    /// The repeat mode change notification summary.
209    /// you can use the placeholders like "{repeat}" in the summary, it will be replaced with the repeat mode.
210    #[arg(short = 'G', long)]
211    repeat_notification_summary: Option<String>,
212    /// The time out of the repeat mode change notification, in seconds.
213    #[arg(short = 'H', long)]
214    repeat_notification_timeout: Option<i32>,
215    /// The aaa mode change notification body.
216    /// you can use the placeholders like "{aaa_mode}" in the body, it will be replaced with the aaa mode.
217    ///
218    /// If you leave it empty, the notification will not be shown.
219    #[arg(short = 'A', long)]
220    aaa_mode_notification_body: Option<String>,
221    /// The aaa mode change notification summary.
222    /// you can use the placeholders like "{aaa_mode}" in the summary, it will be replaced with the aaa mode.
223    #[arg(short = 'D', long)]
224    aaa_mode_notification_summary: Option<String>,
225    /// The time out of the aaa mode change notification, in seconds.
226    #[arg(short = 'F', long)]
227    aaa_mode_notification_timeout: Option<i32>,
228    #[cfg(feature = "lyrics")]
229    /// The lyrics notification body, if you want to show the lyrics separate notification.
230    /// you can use the placeholders like "{lyrics}" in the body, it will be replaced with the lyrics.
231    ///
232    /// If you leave it empty, the notification will not be shown.
233    #[arg(short = 'L', long)]
234    lyrics_notification_body: Option<String>,
235    #[cfg(feature = "lyrics")]
236    /// The lyrics notification summary, if you want to show the lyrics separate notification.
237    /// you can use the placeholders like "{lyrics}" in the summary, it will be replaced with the lyrics.
238    #[arg(short = 'M', long)]
239    lyrics_notification_summary: Option<String>,
240    /// The status change notification body.
241    /// you can use the placeholders like "{status}" in the body, it will be replaced with the aaa mode.
242    ///
243    /// If you leave it empty, the notification will not be shown.
244    #[arg(short = 'O', long)]
245    status_notification_body: Option<String>,
246    /// The status change notification summary.
247    /// you can use the placeholders like "{status}" in the summary, it will be replaced with the aaa mode.
248    #[arg(short = 'P', long)]
249    status_notification_summary: Option<String>,
250    /// The time out of the status change notification, in seconds.
251    #[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    /// Use a custom config path
258    #[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    /// Load the config file and parse the args.
321    /// And combine them together.
322    /// The args will override the config.
323    /// If the config file is not found, create a new one, and use the default values.
324    /// If the config file is found, but the config is invalid, use the default values.
325    #[inline(always)]
326    pub fn load_config_and_parse_args() -> Self {
327        #[cfg(feature = "debug")]
328        info!("Parsing args...");
329
330        // parse the args
331        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        // load config file
354        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        // Combine the config and args(the args will override the config)
368        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}