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"
28 #include "utility/conversion.h"
29 #include "utility/option_parser.h"
30 #include "utility/type_conversions.h"
32 #ifdef HAVE_LANGINFO_H
33 # include <langinfo.h>
40 std::string
remove_dollar_formatting(const std::string
&s
)
43 for (size_t i
= 0; i
< s
.size(); ++i
)
53 std::pair
<std::vector
<Column
>, std::string
> generate_columns(const std::string
&format
)
55 std::vector
<Column
> result
;
58 while (!(width
= getEnclosedString(format
, '(', ')', &pos
)).empty())
61 col
.color
= stringToColor(getEnclosedString(format
, '[', ']', &pos
));
62 std::string tag_type
= getEnclosedString(format
, '{', '}', &pos
);
64 if (*width
.rbegin() == 'f')
67 width
.resize(width
.size()-1);
73 size_t tag_type_colon_pos
= tag_type
.find(':');
74 if (tag_type_colon_pos
!= std::string::npos
)
76 col
.name
= ToWString(tag_type
.substr(tag_type_colon_pos
+1));
77 tag_type
.resize(tag_type_colon_pos
);
80 if (!tag_type
.empty())
84 // extract tag types in format a|b|c etc.
86 col
.type
+= tag_type
[(++i
)++]; // nice one.
87 while (tag_type
[i
] == '|');
90 for (; i
< tag_type
.length(); ++i
)
95 col
.right_alignment
= 1;
98 col
.display_empty_tag
= 0;
104 col
.display_empty_tag
= 0;
106 col
.width
= boost::lexical_cast
<int>(width
);
107 result
.push_back(col
);
110 // calculate which column is the last one to have relative width and stretch it accordingly
113 int stretch_limit
= 0;
114 auto it
= result
.rbegin();
115 for (; it
!= result
.rend(); ++it
)
118 stretch_limit
+= it
->width
;
122 // if it's false, all columns are fixed
123 if (it
!= result
.rend())
124 it
->stretch_limit
= stretch_limit
;
127 std::string string_format
;
128 // generate format for converting tags in columns to string for Playlist::SongInColumnsToString()
129 char tag
[] = "{% }|";
131 for (auto it
= result
.begin(); it
!= result
.end(); ++it
)
133 for (std::string::const_iterator j
= it
->type
.begin(); j
!= it
->type
.end(); ++j
)
136 string_format
+= tag
;
138 *string_format
.rbegin() = ' ';
140 if (string_format
.length() == 1) // only '{'
141 string_format
+= '}';
143 *string_format
.rbegin() = '}';
145 return std::make_pair(std::move(result
), std::move(string_format
));
148 void add_slash_at_the_end(std::string
&s
)
150 if (s
.empty() || *s
.rbegin() != '/')
152 s
.resize(s
.size()+1);
157 std::string
adjust_directory(std::string
&&s
)
159 add_slash_at_the_end(s
);
164 std::string
adjust_and_validate_format(std::string
&&format
)
166 MPD::Song::validateFormat(format
);
167 format
= "{" + format
+ "}";
171 // parser worker for buffer
172 template <typename ValueT
, typename TransformT
>
173 option_parser::worker
buffer(NC::Buffer
&arg
, ValueT
&&value
, TransformT
&&map
)
175 return option_parser::worker(assign
<std::string
>(arg
, [&arg
, map
](std::string
&&s
) {
176 return map(stringToBuffer(s
));
177 }), defaults_to(arg
, map(std::forward
<ValueT
>(value
))));
182 bool Configuration::read(const std::string
&config_path
)
184 std::string mpd_host
;
186 std::string columns_format
;
188 // keep the same order of variables as in configuration file
190 p
.add("ncmpcpp_directory", assign_default
<std::string
>(
191 ncmpcpp_directory
, "~/.ncmpcpp/", adjust_directory
193 p
.add("lyrics_directory", assign_default
<std::string
>(
194 lyrics_directory
, "~/.lyrics/", adjust_directory
196 p
.add("mpd_host", assign_default
<std::string
>(
197 mpd_host
, "localhost", [](std::string
&&host
) {
198 Mpd
.SetHostname(host
);
201 p
.add("mpd_port", assign_default
<unsigned>(
202 mpd_port
, 6600, [](unsigned port
) {
206 p
.add("mpd_music_dir", assign_default
<std::string
>(
207 mpd_music_dir
, "~/music", adjust_directory
209 p
.add("mpd_connection_timeout", assign_default(
210 mpd_connection_timeout
, 5
212 p
.add("mpd_crossfade_time", assign_default(
215 p
.add("visualizer_fifo_path", assign_default(
216 visualizer_fifo_path
, "/tmp/mpd.fifo"
218 p
.add("visualizer_output_name", assign_default(
219 visualizer_output_name
, "Visualizer feed"
221 p
.add("visualizer_in_stereo", yes_no(
222 visualizer_in_stereo
, true
224 p
.add("visualizer_sample_multiplier", assign_default
<double>(
225 visualizer_sample_multiplier
, 1.0, [](double v
) {
226 lowerBoundCheck(v
, 0.0);
229 p
.add("visualizer_sync_interval", assign_default
<unsigned>(
230 visualizer_sync_interval
, 30, [](unsigned v
) {
231 return boost::posix_time::seconds(v
);
233 p
.add("visualizer_type", option_parser::worker([this](std::string
&&v
) {
235 visualizer_use_wave
= true;
236 else if (v
== "spectrum")
237 visualizer_use_wave
= false;
239 throw std::runtime_error("invalid argument: " + v
);
240 }, defaults_to(visualizer_use_wave
, true)
242 p
.add("visualizer_look", assign_default
<std::string
>(
243 visualizer_chars
, "●▮", [](std::string
&&s
) {
244 auto result
= ToWString(std::move(s
));
245 typedef std::wstring::size_type size_type
;
246 boundsCheck(result
.size(), size_type(2), size_type(2));
249 p
.add("system_encoding", assign_default
<std::string
>(
250 system_encoding
, "", [](std::string
&&enc
) {
251 # ifdef HAVE_LANGINFO_H
252 // try to autodetect system encoding
255 enc
= nl_langinfo(CODESET
);
256 if (enc
== "UTF-8") // mpd uses utf-8 by default so no need to convert
259 # endif // HAVE_LANGINFO_H
262 p
.add("playlist_disable_highlight_delay", assign_default
<unsigned>(
263 playlist_disable_highlight_delay
, 5, [](unsigned v
) {
264 return boost::posix_time::seconds(v
);
266 p
.add("message_delay_time", assign_default(
267 message_delay_time
, 5
269 p
.add("song_list_format", assign_default
<std::string
>(
270 song_list_format
, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [this](std::string
&&s
) {
271 auto result
= adjust_and_validate_format(std::move(s
));
272 song_list_format_dollar_free
= remove_dollar_formatting(result
);
275 p
.add("song_status_format", assign_default
<std::string
>(
276 song_status_format
, "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string
&&s
) {
277 auto result
= adjust_and_validate_format(std::move(s
));
278 if (result
.find("$") != std::string::npos
)
279 song_status_format_no_colors
= stringToBuffer(result
).str();
281 song_status_format_no_colors
= result
;
284 p
.add("song_library_format", assign_default
<std::string
>(
285 song_library_format
, "{%n - }{%t}|{%f}", adjust_and_validate_format
287 p
.add("tag_editor_album_format", assign_default
<std::string
>(
288 tag_editor_album_format
, "{(%y) }%b", adjust_and_validate_format
290 p
.add("browser_sort_mode", assign_default(
291 browser_sort_mode
, SortMode::Name
293 p
.add("browser_sort_format", assign_default
<std::string
>(
294 browser_sort_format
, "{%a - }{%t}|{%f} {(%l)}", adjust_and_validate_format
296 p
.add("alternative_header_first_line_format", assign_default
<std::string
>(
297 new_header_first_line
, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", adjust_and_validate_format
299 p
.add("alternative_header_second_line_format", assign_default
<std::string
>(
300 new_header_second_line
, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", adjust_and_validate_format
302 p
.add("now_playing_prefix", buffer(
303 now_playing_prefix
, NC::Buffer(NC::Format::Bold
), [this](NC::Buffer
&&buf
) {
304 now_playing_prefix_length
= wideLength(ToWString(buf
.str()));
307 p
.add("now_playing_suffix", buffer(
308 now_playing_suffix
, NC::Buffer(NC::Format::NoBold
), [this](NC::Buffer
&&buf
) {
309 now_playing_suffix_length
= wideLength(ToWString(buf
.str()));
312 p
.add("browser_playlist_prefix", buffer(
313 browser_playlist_prefix
, NC::Buffer(NC::Color::Red
, "playlist", NC::Color::End
, ' '), id_()
315 p
.add("selected_item_prefix", buffer(
316 selected_item_prefix
, NC::Buffer(NC::Color::Magenta
), [this](NC::Buffer
&&buf
) {
317 selected_item_prefix_length
= wideLength(ToWString(buf
.str()));
320 p
.add("selected_item_suffix", buffer(
321 selected_item_suffix
, NC::Buffer(NC::Color::End
), [this](NC::Buffer
&&buf
) {
322 selected_item_suffix_length
= wideLength(ToWString(buf
.str()));
325 p
.add("modified_item_prefix", buffer(
326 modified_item_prefix
, NC::Buffer(NC::Color::Green
, "> ", NC::Color::End
), id_()
328 p
.add("song_window_title_format", assign_default
<std::string
>(
329 song_window_title_format
, "{%a - }{%t}|{%f}", adjust_and_validate_format
331 p
.add("song_columns_list_format", assign_default
<std::string
>(
332 columns_format
, "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
333 [this](std::string
&&v
) {
334 boost::tie(columns
, song_in_columns_to_string_format
) = generate_columns(v
);
337 p
.add("execute_on_song_change", assign_default(
338 execute_on_song_change
, ""
340 p
.add("playlist_show_remaining_time", yes_no(
341 playlist_show_remaining_time
, false
343 p
.add("playlist_shorten_total_times", yes_no(
344 playlist_shorten_total_times
, false
346 p
.add("playlist_separate_albums", yes_no(
347 playlist_separate_albums
, false
349 p
.add("playlist_display_mode", assign_default(
350 playlist_display_mode
, DisplayMode::Columns
352 p
.add("browser_display_mode", assign_default(
353 browser_display_mode
, DisplayMode::Classic
355 p
.add("search_engine_display_mode", assign_default(
356 search_engine_display_mode
, DisplayMode::Classic
358 p
.add("playlist_editor_display_mode", assign_default(
359 playlist_editor_display_mode
, DisplayMode::Classic
361 p
.add("discard_colors_if_item_is_selected", yes_no(
362 discard_colors_if_item_is_selected
, true
364 p
.add("incremental_seeking", yes_no(
365 incremental_seeking
, true
367 p
.add("seek_time", assign_default(
370 p
.add("volume_change_step", assign_default(
371 volume_change_step
, 2
373 p
.add("autocenter_mode", yes_no(
374 autocenter_mode
, false
376 p
.add("centered_cursor", yes_no(
377 centered_cursor
, false
379 p
.add("progressbar_look", assign_default
<std::string
>(
380 progressbar
, "=>", [](std::string
&&s
) {
381 auto result
= ToWString(std::move(s
));
382 typedef std::wstring::size_type size_type
;
383 boundsCheck(result
.size(), size_type(2), size_type(3));
384 // if two characters were specified, add third one (null)
388 p
.add("progressbar_boldness", yes_no(
389 progressbar_boldness
, true
391 p
.add("default_place_to_search_in", option_parser::worker([this](std::string
&&v
) {
394 else if (v
== "playlist")
397 throw std::runtime_error("invalid argument: " + v
);
398 }, defaults_to(search_in_db
, true)
400 p
.add("user_interface", assign_default(
401 design
, Design::Classic
403 p
.add("media_library_primary_tag", option_parser::worker([this](std::string
&&v
) {
405 media_lib_primary_tag
= MPD_TAG_ARTIST
;
406 else if (v
== "album_artist")
407 media_lib_primary_tag
= MPD_TAG_ALBUM_ARTIST
;
408 else if (v
== "date")
409 media_lib_primary_tag
= MPD_TAG_DATE
;
410 else if (v
== "genre")
411 media_lib_primary_tag
= MPD_TAG_GENRE
;
412 else if (v
== "composer")
413 media_lib_primary_tag
= MPD_TAG_COMPOSER
;
414 else if (v
== "performer")
415 media_lib_primary_tag
= MPD_TAG_PERFORMER
;
417 throw std::runtime_error("invalid argument: " + v
);
418 regex_type
|= boost::regex::icase
;
419 }, defaults_to(regex_type
, boost::regex::literal
| boost::regex::icase
)
421 p
.add("default_find_mode", option_parser::worker([this](std::string
&&v
) {
423 wrapped_search
= true;
424 else if (v
== "normal")
425 wrapped_search
= false;
427 throw std::runtime_error("invalid argument: " + v
);
428 }, defaults_to(wrapped_search
, true)
430 p
.add("default_space_mode", option_parser::worker([this](std::string
&&v
) {
432 space_selects
= false;
433 else if (v
== "select")
434 space_selects
= true;
436 throw std::runtime_error("invalid argument: " + v
);
437 }, defaults_to(wrapped_search
, true)
439 p
.add("default_tag_editor_pattern", assign_default(
442 p
.add("header_visibility", yes_no(
443 header_visibility
, true
445 p
.add("statusbar_visibility", yes_no(
446 statusbar_visibility
, true
448 p
.add("titles_visibility", yes_no(
449 titles_visibility
, true
451 p
.add("header_text_scrolling", yes_no(
452 header_text_scrolling
, true
454 p
.add("cyclic_scrolling", yes_no(
455 use_cyclic_scrolling
, false
457 p
.add("lines_scrolled", assign_default(
460 p
.add("follow_now_playing_lyrics", yes_no(
461 now_playing_lyrics
, false
463 p
.add("fetch_lyrics_for_current_song_in_background", yes_no(
464 fetch_lyrics_in_background
, false
466 p
.add("store_lyrics_in_song_dir", yes_no(
467 store_lyrics_in_song_dir
, false
469 p
.add("generate_win32_compatible_filenames", yes_no(
470 generate_win32_compatible_filenames
, true
472 p
.add("allow_for_physical_item_deletion", yes_no(
473 allow_for_physical_item_deletion
, false
475 p
.add("lastfm_preferred_language", assign_default(
476 lastfm_preferred_language
, "en"
478 p
.add("space_add_mode", assign_default(
479 space_add_mode
, SpaceAddMode::AlwaysAdd
481 p
.add("show_hidden_files_in_local_browser", yes_no(
482 local_browser_show_hidden_files
, false
484 p
.add("screen_switcher_mode", option_parser::worker([this](std::string
&&v
) {
486 screen_switcher_previous
= true;
489 screen_switcher_previous
= false;
490 boost::sregex_token_iterator
i(v
.begin(), v
.end(), boost::regex("\\w+")), j
;
493 auto screen
= stringtoStartupScreenType(*i
);
494 if (screen
!= ScreenType::Unknown
)
495 screen_sequence
.push_back(screen
);
497 throw std::runtime_error("unknown screen: " + *i
);
501 screen_switcher_previous
= false;
502 screen_sequence
= { ScreenType::Playlist
, ScreenType::Browser
};
504 p
.add("startup_screen", option_parser::worker([this](std::string
&&v
) {
505 startup_screen_type
= stringtoStartupScreenType(v
);
506 if (startup_screen_type
== ScreenType::Unknown
)
507 throw std::runtime_error("unknown screen: " + v
);
508 }, defaults_to(startup_screen_type
, ScreenType::Playlist
)
510 p
.add("locked_screen_width_part", assign_default
<double>(
511 locked_screen_width_part
, 50.0, [](double v
) {
514 p
.add("ask_for_locked_screen_width_part", yes_no(
515 ask_for_locked_screen_width_part
, true
517 p
.add("jump_to_now_playing_song_at_start", yes_no(
518 jump_to_now_playing_song_at_start
, true
520 p
.add("ask_before_clearing_playlists", yes_no(
521 ask_before_clearing_playlists
, true
523 p
.add("clock_display_seconds", yes_no(
524 clock_display_seconds
, false
526 p
.add("display_volume_level", yes_no(
527 display_volume_level
, true
529 p
.add("display_bitrate", yes_no(
530 display_bitrate
, false
532 p
.add("display_remaining_time", yes_no(
533 display_remaining_time
, false
535 p
.add("regular_expressions", option_parser::worker([this](std::string
&&v
) {
537 regex_type
= boost::regex::literal
;
538 else if (v
== "basic")
539 regex_type
= boost::regex::basic
;
540 else if (v
== "extended")
541 regex_type
= boost::regex::extended
;
543 throw std::runtime_error("invalid argument: " + v
);
544 regex_type
|= boost::regex::icase
;
545 }, defaults_to(regex_type
, boost::regex::literal
| boost::regex::icase
)
547 p
.add("ignore_leading_the", yes_no(
548 ignore_leading_the
, false
550 p
.add("block_search_constraints_change_if_items_found", yes_no(
551 block_search_constraints_change
, true
553 p
.add("mouse_support", yes_no(
556 p
.add("mouse_list_scroll_whole_page", yes_no(
557 mouse_list_scroll_whole_page
, true
559 p
.add("empty_tag_marker", assign_default(
562 p
.add("tags_separator", assign_default(
563 tags_separator
, " | "
565 p
.add("tag_editor_extended_numeration", yes_no(
566 tag_editor_extended_numeration
, false
568 p
.add("media_library_sort_by_mtime", yes_no(
569 media_library_sort_by_mtime
, false
571 p
.add("enable_window_title", yes_no(
572 set_window_title
, true
574 p
.add("search_engine_default_search_mode", assign_default
<unsigned>(
575 search_engine_default_search_mode
, 1, [](unsigned v
) {
576 boundsCheck(v
, 1u, 3u);
579 p
.add("external_editor", assign_default(
580 external_editor
, "nano"
582 p
.add("use_console_editor", yes_no(
583 use_console_editor
, true
585 p
.add("colors_enabled", yes_no(
588 p
.add("empty_tag_color", assign_default(
589 empty_tags_color
, NC::Color::Cyan
591 p
.add("header_window_color", assign_default(
592 header_color
, NC::Color::Default
594 p
.add("volume_color", assign_default(
595 volume_color
, NC::Color::Default
597 p
.add("state_line_color", assign_default(
598 state_line_color
, NC::Color::Default
600 p
.add("state_flags_color", assign_default(
601 state_flags_color
, NC::Color::Default
603 p
.add("main_window_color", assign_default(
604 main_color
, NC::Color::Yellow
606 p
.add("color1", assign_default(
607 color1
, NC::Color::White
609 p
.add("color2", assign_default(
610 color2
, NC::Color::Green
612 p
.add("main_window_highlight_color", assign_default(
613 main_highlight_color
, NC::Color::Yellow
615 p
.add("progressbar_color", assign_default(
616 progressbar_color
, NC::Color::Black
618 p
.add("progressbar_elapsed_color", assign_default(
619 progressbar_elapsed_color
, NC::Color::Green
621 p
.add("statusbar_color", assign_default(
622 statusbar_color
, NC::Color::Default
624 p
.add("alternative_ui_separator_color", assign_default(
625 alternative_ui_separator_color
, NC::Color::Black
627 p
.add("active_column_color", assign_default(
628 active_column_color
, NC::Color::Red
630 p
.add("visualizer_color", assign_default(
631 visualizer_color
, NC::Color::Yellow
633 p
.add("window_border_color", assign_default(
634 window_border
, NC::Border::Green
636 p
.add("active_window_border", assign_default(
637 active_window_border
, NC::Border::Red
640 std::ifstream
f(config_path
);