1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <boost/tuple/tuple.hpp>
22 #include <boost/tokenizer.hpp>
26 #include "configuration.h"
27 #include "format_impl.h"
30 #include "utility/conversion.h"
31 #include "utility/option_parser.h"
32 #include "utility/type_conversions.h"
34 #ifdef HAVE_LANGINFO_H
35 # include <langinfo.h>
38 namespace ph
= std::placeholders
;
44 std::vector
<Column
> generate_columns(const std::string
&format
)
46 std::vector
<Column
> result
;
49 while (!(width
= getEnclosedString(format
, '(', ')', &pos
)).empty())
52 auto scolor
= getEnclosedString(format
, '[', ']', &pos
);
54 col
.color
= NC::Color::Default
;
56 col
.color
= boost::lexical_cast
<NC::Color
>(scolor
);
58 if (*width
.rbegin() == 'f')
61 width
.resize(width
.size()-1);
66 auto tag_type
= getEnclosedString(format
, '{', '}', &pos
);
68 size_t tag_type_colon_pos
= tag_type
.find(':');
69 if (tag_type_colon_pos
!= std::string::npos
)
71 col
.name
= ToWString(tag_type
.substr(tag_type_colon_pos
+1));
72 tag_type
.resize(tag_type_colon_pos
);
75 if (!tag_type
.empty())
79 // extract tag types in format a|b|c etc.
81 col
.type
+= tag_type
[(++i
)++]; // nice one.
82 while (tag_type
[i
] == '|');
85 for (; i
< tag_type
.length(); ++i
)
90 col
.right_alignment
= 1;
93 col
.display_empty_tag
= 0;
99 col
.display_empty_tag
= 0;
101 col
.width
= boost::lexical_cast
<int>(width
);
102 result
.push_back(col
);
105 // calculate which column is the last one to have relative width and stretch it accordingly
108 int stretch_limit
= 0;
109 auto it
= result
.rbegin();
110 for (; it
!= result
.rend(); ++it
)
113 stretch_limit
+= it
->width
;
117 // if it's false, all columns are fixed
118 if (it
!= result
.rend())
119 it
->stretch_limit
= stretch_limit
;
125 Format::AST
<char> columns_to_format(const std::vector
<Column
> &columns
)
127 std::vector
<Format::Expression
<char>> result
;
129 auto column
= columns
.begin();
132 Format::FirstOf
<char> first_of
;
133 for (const auto &type
: column
->type
)
135 auto f
= charToGetFunction(type
);
136 assert(f
!= nullptr);
137 first_of
.base().push_back(f
);
139 result
.push_back(std::move(first_of
));
141 if (++column
!= columns
.end())
142 result
.push_back(" ");
147 return Format::AST
<char>(std::move(result
));
150 void add_slash_at_the_end(std::string
&s
)
152 if (s
.empty() || *s
.rbegin() != '/')
154 s
.resize(s
.size()+1);
159 std::string
adjust_directory(std::string s
)
161 add_slash_at_the_end(s
);
166 std::string
adjust_path(std::string s
)
172 NC::Buffer
buffer(const std::string
&v
)
176 Format::parse(v
, Format::Flags::Color
| Format::Flags::Format
),
182 NC::Buffer
buffer_wlength(const NC::Buffer
*target
,
184 const std::string
&v
)
186 // Compatibility layer between highlight color and new highlight prefix and
187 // suffix. Basically, for older configurations if highlight color is provided,
188 // we don't want to override it with default prefix and suffix.
189 if (target
== nullptr || target
->empty())
191 NC::Buffer result
= buffer(v
);
192 wlength
= wideLength(ToWString(result
.str()));
199 void deprecated(const char *option
, double version_removal
, const std::string
&advice
)
201 std::cerr
<< "WARNING: Variable '" << option
202 << "' is deprecated and will be removed in "
205 std::cerr
<< " (" << advice
<< ")";
211 bool Configuration::read(const std::vector
<std::string
> &config_paths
, bool ignore_errors
)
215 // Deprecated options.
216 p
.add
<void>("visualizer_sample_multiplier", nullptr, "", [](std::string v
) {
219 "visualizer_sample_multiplier",
221 "visualizer scales automatically");
223 p
.add
<void>("progressbar_boldness", nullptr, "", [](std::string v
) {
226 "progressbar_boldness",
228 "use extended progressbar_color and progressbar_elapsed_color instead");
231 p
.add
<void>("main_window_highlight_color", nullptr, "", [this](std::string v
) {
234 const std::string current_item_prefix_str
= "$(" + v
+ ")$r";
235 const std::string current_item_suffix_str
= "$/r$(end)";
236 current_item_prefix
= buffer_wlength(
238 current_item_prefix_length
,
239 current_item_prefix_str
);
240 current_item_suffix
= buffer_wlength(
242 current_item_suffix_length
,
243 current_item_suffix_str
);
245 "main_window_highlight_color",
247 "set current_item_prefix = \""
248 + current_item_prefix_str
249 + "\" and current_item_suffix = \""
250 + current_item_suffix_str
251 + "\" to preserve current behavior");
254 p
.add
<void>("active_column_color", nullptr, "", [this](std::string v
) {
258 "active_column_color",
260 "replaced by current_item_inactive_column_prefix"
261 " and current_item_inactive_column_suffix");
265 // keep the same order of variables as in configuration file
266 p
.add("ncmpcpp_directory", &ncmpcpp_directory
, "~/.ncmpcpp/", adjust_directory
);
267 p
.add("lyrics_directory", &lyrics_directory
, "~/.lyrics/", adjust_directory
);
268 p
.add
<void>("mpd_host", nullptr, "localhost", [](std::string host
) {
270 Mpd
.SetHostname(host
);
272 p
.add
<void>("mpd_port", nullptr, "6600", [](std::string port
) {
273 Mpd
.SetPort(verbose_lexical_cast
<unsigned>(port
));
275 p
.add("mpd_music_dir", &mpd_music_dir
, "~/music", adjust_directory
);
276 p
.add("mpd_connection_timeout", &mpd_connection_timeout
, "5");
277 p
.add("mpd_crossfade_time", &crossfade_time
, "5");
278 p
.add("visualizer_fifo_path", &visualizer_fifo_path
, "/tmp/mpd.fifo", adjust_path
);
279 p
.add("visualizer_output_name", &visualizer_output_name
, "Visualizer feed");
280 p
.add("visualizer_in_stereo", &visualizer_in_stereo
, "yes", yes_no
);
281 p
.add("visualizer_sync_interval", &visualizer_sync_interval
, "30",
283 unsigned sync_interval
= verbose_lexical_cast
<unsigned>(v
);
284 lowerBoundCheck
<unsigned>(sync_interval
, 10);
285 return boost::posix_time::seconds(sync_interval
);
287 p
.add("visualizer_type", &visualizer_type
, "wave");
288 p
.add("visualizer_look", &visualizer_chars
, "●▮", [](std::string s
) {
289 auto result
= ToWString(std::move(s
));
290 boundsCheck
<std::wstring::size_type
>(result
.size(), 2, 2);
293 p
.add("visualizer_color", &visualizer_colors
,
294 "blue, cyan, green, yellow, magenta, red", list_of
<NC::FormattedColor
>);
295 p
.add("system_encoding", &system_encoding
, "", [](std::string encoding
) {
296 #ifdef HAVE_LANGINFO_H
297 // try to autodetect system encoding
298 if (encoding
.empty())
300 encoding
= nl_langinfo(CODESET
);
301 if (encoding
== "UTF-8") // mpd uses utf-8 by default so no need to convert
304 #endif // HAVE_LANGINFO_H
307 p
.add("playlist_disable_highlight_delay", &playlist_disable_highlight_delay
,
308 "5", [](std::string v
) {
309 return boost::posix_time::seconds(verbose_lexical_cast
<unsigned>(v
));
311 p
.add("message_delay_time", &message_delay_time
, "5");
312 p
.add("song_list_format", &song_list_format
,
313 "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [](std::string v
) {
314 return Format::parse(v
);
316 p
.add("song_status_format", &song_status_format
,
317 "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string v
) {
318 auto flags
= Format::Flags::All
^ Format::Flags::OutputSwitch
;
319 // precompute wide format for status display
320 song_status_wformat
= Format::parse(ToWString(v
), flags
);
321 return Format::parse(v
, flags
);
323 p
.add("song_library_format", &song_library_format
,
324 "{%n - }{%t}|{%f}", [](std::string v
) {
325 return Format::parse(v
);
327 p
.add("alternative_header_first_line_format", &new_header_first_line
,
328 "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [](std::string v
) {
329 return Format::parse(ToWString(std::move(v
)),
330 Format::Flags::All
^ Format::Flags::OutputSwitch
);
332 p
.add("alternative_header_second_line_format", &new_header_second_line
,
333 "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [](std::string v
) {
334 return Format::parse(ToWString(std::move(v
)),
335 Format::Flags::All
^ Format::Flags::OutputSwitch
);
337 p
.add("current_item_prefix", ¤t_item_prefix
, "$(yellow)$r",
338 std::bind(buffer_wlength
,
339 ¤t_item_prefix
,
340 std::ref(current_item_prefix_length
),
342 p
.add("current_item_suffix", ¤t_item_suffix
, "$/r$(end)",
343 std::bind(buffer_wlength
,
344 ¤t_item_suffix
,
345 std::ref(current_item_suffix_length
),
347 p
.add("current_item_inactive_column_prefix", ¤t_item_inactive_column_prefix
,
349 std::bind(buffer_wlength
,
350 ¤t_item_inactive_column_prefix
,
351 std::ref(current_item_inactive_column_prefix_length
),
353 p
.add("current_item_inactive_column_suffix", ¤t_item_inactive_column_suffix
,
355 std::bind(buffer_wlength
,
356 ¤t_item_inactive_column_suffix
,
357 std::ref(current_item_inactive_column_suffix_length
),
359 p
.add("now_playing_prefix", &now_playing_prefix
, "$b",
360 std::bind(buffer_wlength
,
362 std::ref(now_playing_prefix_length
),
364 p
.add("now_playing_suffix", &now_playing_suffix
, "$/b",
365 std::bind(buffer_wlength
,
367 std::ref(now_playing_suffix_length
),
369 p
.add("browser_playlist_prefix", &browser_playlist_prefix
, "$2playlist$9 ", buffer
);
370 p
.add("selected_item_prefix", &selected_item_prefix
, "$6",
371 std::bind(buffer_wlength
,
373 std::ref(selected_item_prefix_length
),
375 p
.add("selected_item_suffix", &selected_item_suffix
, "$9",
376 std::bind(buffer_wlength
,
378 std::ref(selected_item_suffix_length
),
380 p
.add("modified_item_prefix", &modified_item_prefix
, "$3>$9 ", buffer
);
381 p
.add("song_window_title_format", &song_window_title_format
,
382 "{%a - }{%t}|{%f}", [](std::string v
) {
383 return Format::parse(v
, Format::Flags::Tag
);
385 p
.add("browser_sort_mode", &browser_sort_mode
, "name");
386 p
.add("browser_sort_format", &browser_sort_format
,
387 "{%a - }{%t}|{%f} {(%l)}", [](std::string v
) {
388 return Format::parse(v
, Format::Flags::Tag
);
390 p
.add("song_columns_list_format", &song_columns_mode_format
,
391 "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
392 [this](std::string v
) {
393 columns
= generate_columns(v
);
394 return columns_to_format(columns
);
396 p
.add("execute_on_song_change", &execute_on_song_change
, "", adjust_path
);
397 p
.add("execute_on_player_state_change", &execute_on_player_state_change
,
399 p
.add("playlist_show_mpd_host", &playlist_show_mpd_host
, "no", yes_no
);
400 p
.add("playlist_show_remaining_time", &playlist_show_remaining_time
, "no", yes_no
);
401 p
.add("playlist_shorten_total_times", &playlist_shorten_total_times
, "no", yes_no
);
402 p
.add("playlist_separate_albums", &playlist_separate_albums
, "no", yes_no
);
403 p
.add("playlist_display_mode", &playlist_display_mode
, "columns");
404 p
.add("browser_display_mode", &browser_display_mode
, "classic");
405 p
.add("search_engine_display_mode", &search_engine_display_mode
, "classic");
406 p
.add("playlist_editor_display_mode", &playlist_editor_display_mode
, "classic");
407 p
.add("discard_colors_if_item_is_selected", &discard_colors_if_item_is_selected
,
409 p
.add("show_duplicate_tags", &MPD::Song::ShowDuplicateTags
, "yes", yes_no
);
410 p
.add("incremental_seeking", &incremental_seeking
, "yes", yes_no
);
411 p
.add("seek_time", &seek_time
, "1");
412 p
.add("volume_change_step", &volume_change_step
, "2");
413 p
.add("autocenter_mode", &autocenter_mode
, "no", yes_no
);
414 p
.add("centered_cursor", ¢ered_cursor
, "no", yes_no
);
415 p
.add("progressbar_look", &progressbar
, "=>", [](std::string v
) {
416 auto result
= ToWString(std::move(v
));
417 boundsCheck
<std::wstring::size_type
>(result
.size(), 2, 3);
418 // If two characters were specified, fill \0 as the third one.
422 p
.add("default_place_to_search_in", &search_in_db
, "database", [](std::string v
) {
425 else if (v
== "playlist")
430 p
.add("user_interface", &design
, "classic");
431 p
.add("data_fetching_delay", &data_fetching_delay
, "yes", yes_no
);
432 p
.add("media_library_primary_tag", &media_lib_primary_tag
, "artist", [](std::string v
) {
434 return MPD_TAG_ARTIST
;
435 else if (v
== "album_artist")
436 return MPD_TAG_ALBUM_ARTIST
;
437 else if (v
== "date")
439 else if (v
== "genre")
440 return MPD_TAG_GENRE
;
441 else if (v
== "composer")
442 return MPD_TAG_COMPOSER
;
443 else if (v
== "performer")
444 return MPD_TAG_PERFORMER
;
448 p
.add("media_library_albums_split_by_date", &media_library_albums_split_by_date
, "yes", yes_no
);
449 p
.add("default_find_mode", &wrapped_search
, "wrapped", [](std::string v
) {
452 else if (v
== "normal")
457 p
.add("default_tag_editor_pattern", &pattern
, "%n - %t");
458 p
.add("header_visibility", &header_visibility
, "yes", yes_no
);
459 p
.add("statusbar_visibility", &statusbar_visibility
, "yes", yes_no
);
460 p
.add("titles_visibility", &titles_visibility
, "yes", yes_no
);
461 p
.add("header_text_scrolling", &header_text_scrolling
, "yes", yes_no
);
462 p
.add("cyclic_scrolling", &use_cyclic_scrolling
, "no", yes_no
);
463 p
.add("lines_scrolled", &lines_scrolled
, "2");
464 p
.add("lyrics_fetchers", &lyrics_fetchers
,
465 "lyricwiki, azlyrics, genius, sing365, lyricsmania, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, internet",
466 list_of
<LyricsFetcher_
>);
467 p
.add("follow_now_playing_lyrics", &now_playing_lyrics
, "no", yes_no
);
468 p
.add("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background
,
470 p
.add("store_lyrics_in_song_dir", &store_lyrics_in_song_dir
, "no", yes_no
);
471 p
.add("generate_win32_compatible_filenames", &generate_win32_compatible_filenames
,
473 p
.add("allow_for_physical_item_deletion", &allow_for_physical_item_deletion
,
475 p
.add("lastfm_preferred_language", &lastfm_preferred_language
, "en");
476 p
.add("space_add_mode", &space_add_mode
, "add_remove");
477 p
.add("show_hidden_files_in_local_browser", &local_browser_show_hidden_files
,
480 "screen_switcher_mode", nullptr, "playlist, browser",
481 [this](std::string v
) {
483 screen_switcher_previous
= true;
486 screen_switcher_previous
= false;
487 screen_sequence
= list_of
<ScreenType
>(v
, [](std::string s
) {
488 auto screen
= stringtoStartupScreenType(s
);
489 if (screen
== ScreenType::Unknown
)
495 p
.add("startup_screen", &startup_screen_type
, "playlist", [](std::string v
) {
496 auto screen
= stringtoStartupScreenType(v
);
497 if (screen
== ScreenType::Unknown
)
501 p
.add("startup_slave_screen", &startup_slave_screen_type
, "", [](std::string v
) {
502 boost::optional
<ScreenType
> screen
;
505 screen
= stringtoStartupScreenType(v
);
506 if (screen
== ScreenType::Unknown
)
511 p
.add("startup_slave_screen_focus", &startup_slave_screen_focus
, "no", yes_no
);
512 p
.add("locked_screen_width_part", &locked_screen_width_part
,
513 "50", [](std::string v
) {
514 return verbose_lexical_cast
<double>(v
) / 100;
516 p
.add("ask_for_locked_screen_width_part", &ask_for_locked_screen_width_part
,
518 p
.add("jump_to_now_playing_song_at_start", &jump_to_now_playing_song_at_start
,
520 p
.add("ask_before_clearing_playlists", &ask_before_clearing_playlists
,
522 p
.add("ask_before_shuffling_playlists", &ask_before_shuffling_playlists
,
524 p
.add("clock_display_seconds", &clock_display_seconds
, "no", yes_no
);
525 p
.add("display_volume_level", &display_volume_level
, "yes", yes_no
);
526 p
.add("display_bitrate", &display_bitrate
, "no", yes_no
);
527 p
.add("display_remaining_time", &display_remaining_time
, "no", yes_no
);
528 p
.add("regular_expressions", ®ex_type
, "perl", [](std::string v
) {
530 return boost::regex::icase
| boost::regex::literal
;
531 else if (v
== "basic")
532 return boost::regex::icase
| boost::regex::basic
;
533 else if (v
== "extended")
534 return boost::regex::icase
| boost::regex::extended
;
535 else if (v
== "perl")
536 return boost::regex::icase
| boost::regex::perl
;
540 p
.add("ignore_leading_the", &ignore_leading_the
, "no", yes_no
);
541 p
.add("ignore_diacritics", &ignore_diacritics
, "no", yes_no
);
542 p
.add("block_search_constraints_change_if_items_found",
543 &block_search_constraints_change
, "yes", yes_no
);
544 p
.add("mouse_support", &mouse_support
, "yes", yes_no
);
545 p
.add("mouse_list_scroll_whole_page", &mouse_list_scroll_whole_page
, "yes", yes_no
);
546 p
.add("empty_tag_marker", &empty_tag
, "<empty>");
547 p
.add("tags_separator", &MPD::Song::TagsSeparator
, " | ");
548 p
.add("tag_editor_extended_numeration", &tag_editor_extended_numeration
, "no", yes_no
);
549 p
.add("media_library_sort_by_mtime", &media_library_sort_by_mtime
, "no", yes_no
);
550 p
.add("enable_window_title", &set_window_title
, "yes", [](std::string v
) {
551 // Consider this variable only if TERM variable is available and we're not
552 // in emacs terminal nor tty (through any wrapper like screen).
553 auto term
= getenv("TERM");
555 && strstr(term
, "linux") == nullptr
556 && strncmp(term
, "eterm", const_strlen("eterm")))
560 std::clog
<< "Terminal doesn't support window title, skipping 'enable_window_title'.\n";
564 p
.add("search_engine_default_search_mode", &search_engine_default_search_mode
,
565 "1", [](std::string v
) {
566 auto mode
= verbose_lexical_cast
<unsigned>(v
);
567 boundsCheck
<unsigned>(mode
, 1, 3);
570 p
.add("external_editor", &external_editor
, "nano", adjust_path
);
571 p
.add("use_console_editor", &use_console_editor
, "yes", yes_no
);
572 p
.add("colors_enabled", &colors_enabled
, "yes", yes_no
);
573 p
.add("empty_tag_color", &empty_tags_color
, "cyan");
574 p
.add("header_window_color", &header_color
, "default");
575 p
.add("volume_color", &volume_color
, "default");
576 p
.add("state_line_color", &state_line_color
, "default");
577 p
.add("state_flags_color", &state_flags_color
, "default:b");
578 p
.add("main_window_color", &main_color
, "yellow");
579 p
.add("color1", &color1
, "white");
580 p
.add("color2", &color2
, "green");
581 p
.add("progressbar_color", &progressbar_color
, "black:b");
582 p
.add("progressbar_elapsed_color", &progressbar_elapsed_color
, "green:b");
583 p
.add("statusbar_color", &statusbar_color
, "default");
584 p
.add("statusbar_time_color", &statusbar_time_color
, "default:b");
585 p
.add("player_state_color", &player_state_color
, "default:b");
586 p
.add("alternative_ui_separator_color", &alternative_ui_separator_color
, "black:b");
587 p
.add("window_border_color", &window_border
, "green", verbose_lexical_cast
<NC::Color
>);
588 p
.add("active_window_border", &active_window_border
, "red",
589 verbose_lexical_cast
<NC::Color
>);
592 config_paths
.begin(),
594 [&](const std::string
&config_path
) {
595 std::ifstream
f(config_path
);
597 std::clog
<< "Reading configuration from " << config_path
<< "...\n";
598 return p
.run(f
, ignore_errors
);
600 ) && p
.initialize_undefined(ignore_errors
);