change version to 0.7.3
[ncmpcpp.git] / src / settings.cpp
blob6f3daf529bc76a26f210f5cd40c631131b4a4b5d
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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 <fstream>
23 #include <stdexcept>
25 #include "configuration.h"
26 #include "format_impl.h"
27 #include "helpers.h"
28 #include "settings.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>
35 #endif
37 Configuration Config;
39 namespace {
41 std::vector<Column> generate_columns(const std::string &format)
43 std::vector<Column> result;
44 std::string width;
45 size_t pos = 0;
46 while (!(width = getEnclosedString(format, '(', ')', &pos)).empty())
48 Column col;
49 auto scolor = getEnclosedString(format, '[', ']', &pos);
50 if (scolor.empty())
51 col.color = NC::Color::Default;
52 else
53 col.color = boost::lexical_cast<NC::Color>(scolor);
55 if (*width.rbegin() == 'f')
57 col.fixed = true;
58 width.resize(width.size()-1);
60 else
61 col.fixed = false;
63 auto tag_type = getEnclosedString(format, '{', '}', &pos);
64 // alternative name
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())
74 size_t i = -1;
76 // extract tag types in format a|b|c etc.
78 col.type += tag_type[(++i)++]; // nice one.
79 while (tag_type[i] == '|');
81 // apply attributes
82 for (; i < tag_type.length(); ++i)
84 switch (tag_type[i])
86 case 'r':
87 col.right_alignment = 1;
88 break;
89 case 'E':
90 col.display_empty_tag = 0;
91 break;
95 else // empty column
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
103 if (!result.empty())
105 int stretch_limit = 0;
106 auto it = result.rbegin();
107 for (; it != result.rend(); ++it)
109 if (it->fixed)
110 stretch_limit += it->width;
111 else
112 break;
114 // if it's false, all columns are fixed
115 if (it != result.rend())
116 it->stretch_limit = stretch_limit;
119 return result;
122 Format::AST<char> columns_to_format(const std::vector<Column> &columns)
124 std::vector<Format::Expression<char>> result;
126 auto column = columns.begin();
127 while (true)
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(" ");
140 else
141 break;
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);
152 *s.rbegin() = '/';
156 std::string adjust_directory(std::string s)
158 add_slash_at_the_end(s);
159 expand_home(s);
160 return 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) {
168 NC::Buffer result;
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) {
178 NC::Border result;
179 if (!s.empty())
181 try {
182 result = boost::lexical_cast<NC::Color>(s);
183 } catch (boost::bad_lexical_cast &) {
184 throw std::runtime_error("invalid border: " + s);
187 return result;
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";
195 }, [] { }
201 bool Configuration::read(const std::vector<std::string> &config_paths, bool ignore_errors)
203 std::string mpd_host;
204 unsigned mpd_port;
205 std::string columns_format;
207 option_parser p;
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
222 expand_home(host);
223 Mpd.SetHostname(host);
224 return host;
225 }));
226 p.add("mpd_port", assign_default<unsigned>(
227 mpd_port, 6600, [](unsigned port) {
228 Mpd.SetPort(port);
229 return port;
230 }));
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(
238 crossfade_time, 5
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);
252 return v;
253 }));
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);
258 }));
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));
267 return result;
268 }));
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)
273 try {
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");
281 }, [this] {
282 visualizer_colors = { NC::Color::Blue, NC::Color::Cyan, NC::Color::Green, NC::Color::Yellow, NC::Color::Magenta, NC::Color::Red };
283 }));
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
288 if (enc.empty())
290 enc = nl_langinfo(CODESET);
291 if (enc == "UTF-8") // mpd uses utf-8 by default so no need to convert
292 enc.clear();
294 # endif // HAVE_LANGINFO_H
295 return enc;
296 }));
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);
300 }));
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);
307 }));
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);
314 }));
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);
318 }));
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
324 }));
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
330 }));
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()));
334 return buf;
335 }));
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()));
339 return buf;
340 }));
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()));
347 return buf;
348 }));
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()));
352 return buf;
353 }));
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);
363 }));
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);
367 }));
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);
373 return v;
374 }));
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(
409 seek_time, 1
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)
426 result.resize(3);
427 return result;
428 }));
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) {
433 if (v == "database")
434 search_in_db = true;
435 else if (v == "playlist")
436 search_in_db = true;
437 else
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) {
448 if (v == "artist")
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;
460 else
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) {
465 if (v == "wrapped")
466 wrapped_search = true;
467 else if (v == "normal")
468 wrapped_search = false;
469 else
470 throw std::runtime_error("invalid argument: " + v);
471 }, defaults_to(wrapped_search, true)
473 p.add("default_tag_editor_pattern", assign_default(
474 pattern, "%n - %t"
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(
492 lines_scrolled, 2
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) {
519 if (v == "previous")
520 screen_switcher_previous = true;
521 else
523 screen_switcher_previous = false;
524 boost::sregex_token_iterator i(v.begin(), v.end(), boost::regex("\\w+")), j;
525 for (; i != j; ++i)
527 auto screen = stringtoStartupScreenType(*i);
528 if (screen != ScreenType::Unknown)
529 screen_sequence.push_back(screen);
530 else
531 throw std::runtime_error("unknown screen: " + *i);
534 }, [this] {
535 screen_switcher_previous = false;
536 screen_sequence = { ScreenType::Playlist, ScreenType::Browser };
537 }));
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) {
545 if (!v.empty())
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) {
558 return v / 100;
559 }));
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) {
585 if (v == "none")
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;
593 else
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(
605 mouse_support, true
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(
611 empty_tag, "<empty>"
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
625 // like screen).
626 auto term = getenv("TERM");
627 if (term != nullptr
628 && strstr(term, "linux") == nullptr
629 && strncmp(term, "eterm", const_strlen("eterm")))
630 return yes_no(set_window_title, true);
631 else
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";
638 }());
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);
642 return --v;
643 }));
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(
651 colors_enabled, true
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
702 return std::all_of(
703 config_paths.begin(),
704 config_paths.end(),
705 [&](const std::string &config_path) {
706 std::ifstream f(config_path);
707 if (f.is_open())
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 : */