1 /***************************************************************************
2 * Copyright (C) 2008-2014 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>
25 #include "configuration.h"
26 #include "format_impl.h"
29 #include "utility/conversion.h"
30 #include "utility/option_parser.h"
31 #include "utility/type_conversions.h"
33 #ifdef HAVE_LANGINFO_H
34 # include <langinfo.h>
41 std::vector
<Column
> generate_columns(const std::string
&format
)
43 std::vector
<Column
> result
;
46 while (!(width
= getEnclosedString(format
, '(', ')', &pos
)).empty())
49 auto scolor
= getEnclosedString(format
, '[', ']', &pos
);
51 col
.color
= NC::Color::Default
;
53 col
.color
= boost::lexical_cast
<NC::Color
>(scolor
);
55 if (*width
.rbegin() == 'f')
58 width
.resize(width
.size()-1);
63 auto tag_type
= getEnclosedString(format
, '{', '}', &pos
);
65 size_t tag_type_colon_pos
= tag_type
.find(':');
66 if (tag_type_colon_pos
!= std::string::npos
)
68 col
.name
= ToWString(tag_type
.substr(tag_type_colon_pos
+1));
69 tag_type
.resize(tag_type_colon_pos
);
72 if (!tag_type
.empty())
76 // extract tag types in format a|b|c etc.
78 col
.type
+= tag_type
[(++i
)++]; // nice one.
79 while (tag_type
[i
] == '|');
82 for (; i
< tag_type
.length(); ++i
)
87 col
.right_alignment
= 1;
90 col
.display_empty_tag
= 0;
96 col
.display_empty_tag
= 0;
98 col
.width
= boost::lexical_cast
<int>(width
);
99 result
.push_back(col
);
102 // calculate which column is the last one to have relative width and stretch it accordingly
105 int stretch_limit
= 0;
106 auto it
= result
.rbegin();
107 for (; it
!= result
.rend(); ++it
)
110 stretch_limit
+= it
->width
;
114 // if it's false, all columns are fixed
115 if (it
!= result
.rend())
116 it
->stretch_limit
= stretch_limit
;
122 Format::AST
<char> columns_to_format(const std::vector
<Column
> &columns
)
124 std::vector
<Format::Expression
<char>> result
;
126 auto column
= columns
.begin();
129 Format::FirstOf
<char> first_of
;
130 for (const auto &type
: column
->type
)
132 auto f
= charToGetFunction(type
);
133 assert(f
!= nullptr);
134 first_of
.base().push_back(f
);
136 result
.push_back(std::move(first_of
));
138 if (++column
!= columns
.end())
139 result
.push_back(" ");
144 return Format::AST
<char>(std::move(result
));
147 void add_slash_at_the_end(std::string
&s
)
149 if (s
.empty() || *s
.rbegin() != '/')
151 s
.resize(s
.size()+1);
156 std::string
adjust_directory(std::string s
)
158 add_slash_at_the_end(s
);
163 // parser worker for buffer
164 template <typename ValueT
, typename TransformT
>
165 option_parser::worker
buffer(NC::Buffer
&arg
, ValueT
&&value
, TransformT
&&map
)
167 return option_parser::worker(assign
<std::string
>(arg
, [&arg
, map
](std::string s
) {
169 auto ast
= Format::parse(s
, Format::Flags::Color
| Format::Flags::Format
);
170 Format::print(ast
, result
, nullptr);
171 return map(std::move(result
));
172 }), defaults_to(arg
, map(std::forward
<ValueT
>(value
))));
175 option_parser::worker
border(NC::Border
&arg
, NC::Border value
)
177 return option_parser::worker(assign
<std::string
>(arg
, [&arg
](std::string s
) {
182 result
= boost::lexical_cast
<NC::Color
>(s
);
183 } catch (boost::bad_lexical_cast
&) {
184 throw std::runtime_error("invalid border: " + s
);
188 }), defaults_to(arg
, std::move(value
)));
191 option_parser::worker
deprecated(const char *option
, double version_removal
)
193 return option_parser::worker([option
, version_removal
](std::string
) {
194 std::cerr
<< "WARNING: " << option
<< " is deprecated and will be removed in " << version_removal
<< ".\n";
201 bool Configuration::read(const std::vector
<std::string
> &config_paths
, bool ignore_errors
)
203 std::string mpd_host
;
205 std::string columns_format
;
209 // deprecation warnings
210 p
.add("default_space_mode", deprecated("default_space_mode", 0.8));
212 // keep the same order of variables as in configuration file
213 p
.add("ncmpcpp_directory", assign_default
<std::string
>(
214 ncmpcpp_directory
, "~/.ncmpcpp/", adjust_directory
216 p
.add("lyrics_directory", assign_default
<std::string
>(
217 lyrics_directory
, "~/.lyrics/", adjust_directory
219 p
.add("mpd_host", assign_default
<std::string
>(
220 mpd_host
, "localhost", [](std::string host
) {
221 // host can be a path to ipc socket, relative to home directory
223 Mpd
.SetHostname(host
);
226 p
.add("mpd_port", assign_default
<unsigned>(
227 mpd_port
, 6600, [](unsigned port
) {
231 p
.add("mpd_music_dir", assign_default
<std::string
>(
232 mpd_music_dir
, "~/music", adjust_directory
234 p
.add("mpd_connection_timeout", assign_default(
235 mpd_connection_timeout
, 5
237 p
.add("mpd_crossfade_time", assign_default(
240 p
.add("visualizer_fifo_path", assign_default(
241 visualizer_fifo_path
, "/tmp/mpd.fifo"
243 p
.add("visualizer_output_name", assign_default(
244 visualizer_output_name
, "Visualizer feed"
246 p
.add("visualizer_in_stereo", yes_no(
247 visualizer_in_stereo
, true
249 p
.add("visualizer_sample_multiplier", assign_default
<double>(
250 visualizer_sample_multiplier
, 1.0, [](double v
) {
251 lowerBoundCheck(v
, 0.0);
254 p
.add("visualizer_sync_interval", assign_default
<unsigned>(
255 visualizer_sync_interval
, 30, [](unsigned v
) {
256 lowerBoundCheck(v
, 10u);
257 return boost::posix_time::seconds(v
);
259 p
.add("visualizer_type", assign_default(
260 visualizer_type
, VisualizerType::Wave
262 p
.add("visualizer_look", assign_default
<std::string
>(
263 visualizer_chars
, "●▮", [](std::string s
) {
264 auto result
= ToWString(std::move(s
));
265 typedef std::wstring::size_type size_type
;
266 boundsCheck(result
.size(), size_type(2), size_type(2));
269 p
.add("visualizer_color", option_parser::worker([this](std::string v
) {
270 boost::sregex_token_iterator
color(v
.begin(), v
.end(), boost::regex("\\w+")), end
;
271 for (; color
!= end
; ++color
)
274 visualizer_colors
.push_back(boost::lexical_cast
<NC::Color
>(*color
));
275 } catch (boost::bad_lexical_cast
&) {
276 throw std::runtime_error("invalid color: " + *color
);
279 if (visualizer_colors
.empty())
280 throw std::runtime_error("empty list");
282 visualizer_colors
= { NC::Color::Blue
, NC::Color::Cyan
, NC::Color::Green
, NC::Color::Yellow
, NC::Color::Magenta
, NC::Color::Red
};
284 p
.add("system_encoding", assign_default
<std::string
>(
285 system_encoding
, "", [](std::string enc
) {
286 # ifdef HAVE_LANGINFO_H
287 // try to autodetect system encoding
290 enc
= nl_langinfo(CODESET
);
291 if (enc
== "UTF-8") // mpd uses utf-8 by default so no need to convert
294 # endif // HAVE_LANGINFO_H
297 p
.add("playlist_disable_highlight_delay", assign_default
<unsigned>(
298 playlist_disable_highlight_delay
, 5, [](unsigned v
) {
299 return boost::posix_time::seconds(v
);
301 p
.add("message_delay_time", assign_default(
302 message_delay_time
, 5
304 p
.add("song_list_format", assign_default
<std::string
>(
305 song_list_format
, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [](std::string v
) {
306 return Format::parse(v
);
308 p
.add("song_status_format", assign_default
<std::string
>(
309 song_status_format
, "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string v
) {
310 const unsigned flags
= Format::Flags::All
^ Format::Flags::OutputSwitch
;
311 // precompute wide format for status display
312 song_status_wformat
= Format::parse(ToWString(v
), flags
);
313 return Format::parse(v
, flags
);
315 p
.add("song_library_format", assign_default
<std::string
>(
316 song_library_format
, "{%n - }{%t}|{%f}", [](std::string v
) {
317 return Format::parse(v
);
319 p
.add("alternative_header_first_line_format", assign_default
<std::string
>(
320 new_header_first_line
, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [](std::string v
) {
321 return Format::parse(ToWString(std::move(v
)),
322 Format::Flags::All
^ Format::Flags::OutputSwitch
325 p
.add("alternative_header_second_line_format", assign_default
<std::string
>(
326 new_header_second_line
, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [](std::string v
) {
327 return Format::parse(ToWString(std::move(v
)),
328 Format::Flags::All
^ Format::Flags::OutputSwitch
331 p
.add("now_playing_prefix", buffer(
332 now_playing_prefix
, NC::Buffer::init(NC::Format::Bold
), [this](NC::Buffer buf
) {
333 now_playing_prefix_length
= wideLength(ToWString(buf
.str()));
336 p
.add("now_playing_suffix", buffer(
337 now_playing_suffix
, NC::Buffer::init(NC::Format::NoBold
), [this](NC::Buffer buf
) {
338 now_playing_suffix_length
= wideLength(ToWString(buf
.str()));
341 p
.add("browser_playlist_prefix", buffer(
342 browser_playlist_prefix
, NC::Buffer::init(NC::Color::Red
, "playlist", NC::Color::End
, ' '), id_()
344 p
.add("selected_item_prefix", buffer(
345 selected_item_prefix
, NC::Buffer::init(NC::Color::Magenta
), [this](NC::Buffer buf
) {
346 selected_item_prefix_length
= wideLength(ToWString(buf
.str()));
349 p
.add("selected_item_suffix", buffer(
350 selected_item_suffix
, NC::Buffer::init(NC::Color::End
), [this](NC::Buffer buf
) {
351 selected_item_suffix_length
= wideLength(ToWString(buf
.str()));
354 p
.add("modified_item_prefix", buffer(
355 modified_item_prefix
, NC::Buffer::init(NC::Color::Green
, "> ", NC::Color::End
), id_()
357 p
.add("browser_sort_mode", assign_default(
358 browser_sort_mode
, SortMode::Name
360 p
.add("browser_sort_format", assign_default
<std::string
>(
361 browser_sort_format
, "{%a - }{%t}|{%f} {(%l)}", [](std::string v
) {
362 return Format::parse(v
, Format::Flags::Tag
);
364 p
.add("song_window_title_format", assign_default
<std::string
>(
365 song_window_title_format
, "{%a - }{%t}|{%f}", [](std::string v
) {
366 return Format::parse(v
, Format::Flags::Tag
);
368 p
.add("song_columns_list_format", assign_default
<std::string
>(
369 columns_format
, "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
370 [this](std::string v
) {
371 columns
= generate_columns(v
);
372 song_columns_mode_format
= columns_to_format(columns
);
375 p
.add("execute_on_song_change", assign_default(
376 execute_on_song_change
, ""
378 p
.add("playlist_show_mpd_host", yes_no(
379 playlist_show_mpd_host
, false
381 p
.add("playlist_show_remaining_time", yes_no(
382 playlist_show_remaining_time
, false
384 p
.add("playlist_shorten_total_times", yes_no(
385 playlist_shorten_total_times
, false
387 p
.add("playlist_separate_albums", yes_no(
388 playlist_separate_albums
, false
390 p
.add("playlist_display_mode", assign_default(
391 playlist_display_mode
, DisplayMode::Columns
393 p
.add("browser_display_mode", assign_default(
394 browser_display_mode
, DisplayMode::Classic
396 p
.add("search_engine_display_mode", assign_default(
397 search_engine_display_mode
, DisplayMode::Classic
399 p
.add("playlist_editor_display_mode", assign_default(
400 playlist_editor_display_mode
, DisplayMode::Classic
402 p
.add("discard_colors_if_item_is_selected", yes_no(
403 discard_colors_if_item_is_selected
, true
405 p
.add("incremental_seeking", yes_no(
406 incremental_seeking
, true
408 p
.add("seek_time", assign_default(
411 p
.add("volume_change_step", assign_default(
412 volume_change_step
, 2
414 p
.add("autocenter_mode", yes_no(
415 autocenter_mode
, false
417 p
.add("centered_cursor", yes_no(
418 centered_cursor
, false
420 p
.add("progressbar_look", assign_default
<std::string
>(
421 progressbar
, "=>", [](std::string s
) {
422 auto result
= ToWString(std::move(s
));
423 typedef std::wstring::size_type size_type
;
424 boundsCheck(result
.size(), size_type(2), size_type(3));
425 // if two characters were specified, add third one (null)
429 p
.add("progressbar_boldness", yes_no(
430 progressbar_boldness
, true
432 p
.add("default_place_to_search_in", option_parser::worker([this](std::string v
) {
435 else if (v
== "playlist")
438 throw std::runtime_error("invalid argument: " + v
);
439 }, defaults_to(search_in_db
, true)
441 p
.add("user_interface", assign_default(
442 design
, Design::Classic
444 p
.add("data_fetching_delay", yes_no(
445 data_fetching_delay
, true
447 p
.add("media_library_primary_tag", option_parser::worker([this](std::string v
) {
449 media_lib_primary_tag
= MPD_TAG_ARTIST
;
450 else if (v
== "album_artist")
451 media_lib_primary_tag
= MPD_TAG_ALBUM_ARTIST
;
452 else if (v
== "date")
453 media_lib_primary_tag
= MPD_TAG_DATE
;
454 else if (v
== "genre")
455 media_lib_primary_tag
= MPD_TAG_GENRE
;
456 else if (v
== "composer")
457 media_lib_primary_tag
= MPD_TAG_COMPOSER
;
458 else if (v
== "performer")
459 media_lib_primary_tag
= MPD_TAG_PERFORMER
;
461 throw std::runtime_error("invalid argument: " + v
);
462 }, defaults_to(media_lib_primary_tag
, MPD_TAG_ARTIST
)
464 p
.add("default_find_mode", option_parser::worker([this](std::string v
) {
466 wrapped_search
= true;
467 else if (v
== "normal")
468 wrapped_search
= false;
470 throw std::runtime_error("invalid argument: " + v
);
471 }, defaults_to(wrapped_search
, true)
473 p
.add("default_tag_editor_pattern", assign_default(
476 p
.add("header_visibility", yes_no(
477 header_visibility
, true
479 p
.add("statusbar_visibility", yes_no(
480 statusbar_visibility
, true
482 p
.add("titles_visibility", yes_no(
483 titles_visibility
, true
485 p
.add("header_text_scrolling", yes_no(
486 header_text_scrolling
, true
488 p
.add("cyclic_scrolling", yes_no(
489 use_cyclic_scrolling
, false
491 p
.add("lines_scrolled", assign_default(
494 p
.add("follow_now_playing_lyrics", yes_no(
495 now_playing_lyrics
, false
497 p
.add("fetch_lyrics_for_current_song_in_background", yes_no(
498 fetch_lyrics_in_background
, false
500 p
.add("store_lyrics_in_song_dir", yes_no(
501 store_lyrics_in_song_dir
, false
503 p
.add("generate_win32_compatible_filenames", yes_no(
504 generate_win32_compatible_filenames
, true
506 p
.add("allow_for_physical_item_deletion", yes_no(
507 allow_for_physical_item_deletion
, false
509 p
.add("lastfm_preferred_language", assign_default(
510 lastfm_preferred_language
, "en"
512 p
.add("space_add_mode", assign_default(
513 space_add_mode
, SpaceAddMode::AlwaysAdd
515 p
.add("show_hidden_files_in_local_browser", yes_no(
516 local_browser_show_hidden_files
, false
518 p
.add("screen_switcher_mode", option_parser::worker([this](std::string v
) {
520 screen_switcher_previous
= true;
523 screen_switcher_previous
= false;
524 boost::sregex_token_iterator
i(v
.begin(), v
.end(), boost::regex("\\w+")), j
;
527 auto screen
= stringtoStartupScreenType(*i
);
528 if (screen
!= ScreenType::Unknown
)
529 screen_sequence
.push_back(screen
);
531 throw std::runtime_error("unknown screen: " + *i
);
535 screen_switcher_previous
= false;
536 screen_sequence
= { ScreenType::Playlist
, ScreenType::Browser
};
538 p
.add("startup_screen", option_parser::worker([this](std::string v
) {
539 startup_screen_type
= stringtoStartupScreenType(v
);
540 if (startup_screen_type
== ScreenType::Unknown
)
541 throw std::runtime_error("unknown screen: " + v
);
542 }, defaults_to(startup_screen_type
, ScreenType::Playlist
)
544 p
.add("startup_slave_screen", option_parser::worker([this](std::string v
) {
547 startup_slave_screen_type
= stringtoStartupScreenType(v
);
548 if (startup_slave_screen_type
== ScreenType::Unknown
)
549 throw std::runtime_error("unknown slave screen: " + v
);
551 }, defaults_to(startup_slave_screen_type
, boost::none
)
553 p
.add("startup_slave_screen_focus", yes_no(
554 startup_slave_screen_focus
, false
556 p
.add("locked_screen_width_part", assign_default
<double>(
557 locked_screen_width_part
, 50.0, [](double v
) {
560 p
.add("ask_for_locked_screen_width_part", yes_no(
561 ask_for_locked_screen_width_part
, true
563 p
.add("jump_to_now_playing_song_at_start", yes_no(
564 jump_to_now_playing_song_at_start
, true
566 p
.add("ask_before_clearing_playlists", yes_no(
567 ask_before_clearing_playlists
, true
569 p
.add("ask_before_shuffling_playlists", yes_no(
570 ask_before_shuffling_playlists
, true
572 p
.add("clock_display_seconds", yes_no(
573 clock_display_seconds
, false
575 p
.add("display_volume_level", yes_no(
576 display_volume_level
, true
578 p
.add("display_bitrate", yes_no(
579 display_bitrate
, false
581 p
.add("display_remaining_time", yes_no(
582 display_remaining_time
, false
584 p
.add("regular_expressions", option_parser::worker([this](std::string v
) {
586 regex_type
= boost::regex::literal
;
587 else if (v
== "basic")
588 regex_type
= boost::regex::basic
;
589 else if (v
== "extended")
590 regex_type
= boost::regex::extended
;
591 else if (v
== "perl")
592 regex_type
= boost::regex::perl
;
594 throw std::runtime_error("invalid argument: " + v
);
595 regex_type
|= boost::regex::icase
;
596 }, defaults_to(regex_type
, boost::regex::basic
| boost::regex::icase
)
598 p
.add("ignore_leading_the", yes_no(
599 ignore_leading_the
, false
601 p
.add("block_search_constraints_change_if_items_found", yes_no(
602 block_search_constraints_change
, true
604 p
.add("mouse_support", yes_no(
607 p
.add("mouse_list_scroll_whole_page", yes_no(
608 mouse_list_scroll_whole_page
, true
610 p
.add("empty_tag_marker", assign_default(
613 p
.add("tags_separator", assign_default(
614 MPD::Song::TagsSeparator
, " | "
616 p
.add("tag_editor_extended_numeration", yes_no(
617 tag_editor_extended_numeration
, false
619 p
.add("media_library_sort_by_mtime", yes_no(
620 media_library_sort_by_mtime
, false
622 p
.add("enable_window_title", [this]() {
623 // Consider this variable only if TERM variable is available
624 // and we're not in emacs terminal nor tty (through any wrapper
626 auto term
= getenv("TERM");
628 && strstr(term
, "linux") == nullptr
629 && strncmp(term
, "eterm", const_strlen("eterm")))
630 return yes_no(set_window_title
, true);
633 set_window_title
= false;
634 return option_parser::worker([](std::string
) {}, [] {
635 std::clog
<< "Terminal doesn't support window title, skipping 'enable_window_title'.\n";
639 p
.add("search_engine_default_search_mode", assign_default
<unsigned>(
640 search_engine_default_search_mode
, 1, [](unsigned v
) {
641 boundsCheck(v
, 1u, 3u);
644 p
.add("external_editor", assign_default(
645 external_editor
, "nano"
647 p
.add("use_console_editor", yes_no(
648 use_console_editor
, true
650 p
.add("colors_enabled", yes_no(
653 p
.add("empty_tag_color", assign_default(
654 empty_tags_color
, NC::Color::Cyan
656 p
.add("header_window_color", assign_default(
657 header_color
, NC::Color::Default
659 p
.add("volume_color", assign_default(
660 volume_color
, NC::Color::Default
662 p
.add("state_line_color", assign_default(
663 state_line_color
, NC::Color::Default
665 p
.add("state_flags_color", assign_default(
666 state_flags_color
, NC::Color::Default
668 p
.add("main_window_color", assign_default(
669 main_color
, NC::Color::Yellow
671 p
.add("color1", assign_default(
672 color1
, NC::Color::White
674 p
.add("color2", assign_default(
675 color2
, NC::Color::Green
677 p
.add("main_window_highlight_color", assign_default(
678 main_highlight_color
, NC::Color::Yellow
680 p
.add("progressbar_color", assign_default(
681 progressbar_color
, NC::Color::Black
683 p
.add("progressbar_elapsed_color", assign_default(
684 progressbar_elapsed_color
, NC::Color::Green
686 p
.add("statusbar_color", assign_default(
687 statusbar_color
, NC::Color::Default
689 p
.add("alternative_ui_separator_color", assign_default(
690 alternative_ui_separator_color
, NC::Color::Black
692 p
.add("active_column_color", assign_default(
693 active_column_color
, NC::Color::Red
695 p
.add("window_border_color", border(
696 window_border
, NC::Color::Green
698 p
.add("active_window_border", border(
699 active_window_border
, NC::Color::Red
703 config_paths
.begin(),
705 [&](const std::string
&config_path
) {
706 std::ifstream
f(config_path
);
708 std::clog
<< "Reading configuration from " << config_path
<< "...\n";
709 return p
.run(f
, ignore_errors
);
711 ) && p
.initialize_undefined(ignore_errors
);
714 /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab : */