1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "screen_list.h"
36 #include <glib/gstdio.h>
38 #define MAX_LINE_LENGTH 1024
39 #define COMMENT_TOKEN '#'
41 /* configuration field names */
42 #define CONF_ENABLE_COLORS "enable-colors"
43 #define CONF_SCROLL_OFFSET "scroll-offset"
44 #define CONF_AUTO_CENTER "auto-center"
45 #define CONF_WIDE_CURSOR "wide-cursor"
46 #define CONF_KEY_DEFINITION "key"
47 #define CONF_COLOR "color"
48 #define CONF_COLOR_DEFINITION "colordef"
49 #define CONF_LIST_FORMAT "list-format"
50 #define CONF_SEARCH_FORMAT "search-format"
51 #define CONF_STATUS_FORMAT "status-format"
52 #define CONF_XTERM_TITLE_FORMAT "xterm-title-format"
53 #define CONF_LIST_WRAP "wrap-around"
54 #define CONF_FIND_WRAP "find-wrap"
55 #define CONF_FIND_SHOW_LAST "find-show-last"
56 #define CONF_AUDIBLE_BELL "audible-bell"
57 #define CONF_VISIBLE_BELL "visible-bell"
58 #define CONF_BELL_ON_WRAP "bell-on-wrap"
59 #define CONF_STATUS_MESSAGE_TIME "status-message-time"
60 #define CONF_XTERM_TITLE "set-xterm-title"
61 #define CONF_ENABLE_MOUSE "enable-mouse"
62 #define CONF_CROSSFADE_TIME "crossfade-time"
63 #define CONF_SEARCH_MODE "search-mode"
64 #define CONF_HIDE_CURSOR "hide-cursor"
65 #define CONF_SEEK_TIME "seek-time"
66 #define CONF_SCREEN_LIST "screen-list"
67 #define CONF_TIMEDISPLAY_TYPE "timedisplay-type"
68 #define CONF_HOST "host"
69 #define CONF_PORT "port"
70 #define CONF_PASSWORD "password"
71 #define CONF_TIMEOUT "timeout"
72 #define CONF_LYRICS_TIMEOUT "lyrics-timeout"
73 #define CONF_SCROLL "scroll"
74 #define CONF_SCROLL_SEP "scroll-sep"
75 #define CONF_VISIBLE_BITRATE "visible-bitrate"
76 #define CONF_HARDWARE_CURSOR "hardware-cursor"
77 #define CONF_WELCOME_SCREEN_LIST "welcome-screen-list"
78 #define CONF_DISPLAY_TIME "display-time"
79 #define CONF_JUMP_PREFIX_ONLY "jump-prefix-only"
80 #define CONF_LYRICS_AUTOSAVE "lyrics-autosave"
81 #define CONF_LYRICS_SHOW_PLUGIN "lyrics-show-plugin"
82 #define CONF_TEXT_EDITOR "text-editor"
83 #define CONF_TEXT_EDITOR_ASK "text-editor-ask"
84 #define CONF_CHAT_PREFIX "chat-prefix"
85 #define CONF_SECOND_COLUMN "second-column"
89 is_space_not_null(char ch
)
91 unsigned char uch
= (unsigned char)ch
;
92 return uch
<= 0x20 && uch
> 0;
96 * Returns the first non-space character (or a pointer to the null
97 * terminator). Similar to g_strchug(), but just moves the pointer
98 * forward without modifying the string contents.
104 while (is_space_not_null(*p
))
112 return strcasecmp(str
, "yes") == 0 || strcasecmp(str
, "true") == 0 ||
113 strcasecmp(str
, "on") == 0 || strcasecmp(str
, "1") == 0;
117 print_error(const char *msg
, const char *input
)
119 fprintf(stderr
, "%s: %s ('%s')\n",
120 /* To translators: prefix for error messages */
121 _("Error"), msg
, input
);
126 is_word_char(char ch
)
128 return g_ascii_isalnum(ch
) || ch
== '-' || ch
== '_';
132 after_unquoted_word(char *p
)
134 if (!is_word_char(*p
)) {
135 print_error(_("Word expected"), p
);
141 while (is_word_char(*p
))
148 parse_key_value(char *str
, char **end
)
151 if (str
[1] == '\'' || str
[2] != '\'') {
152 print_error(_("Malformed hotkey definition"), str
);
159 long value
= strtol(str
, end
, 0);
161 print_error(_("Malformed hotkey definition"), str
);
170 parse_key_definition(char *str
)
172 /* get the command name */
173 const size_t len
= strlen(str
);
176 char buf
[MAX_LINE_LENGTH
];
177 memset(buf
, 0, MAX_LINE_LENGTH
);
178 while (i
< len
&& str
[i
] != '=' && !g_ascii_isspace(str
[i
]))
181 command_t cmd
= get_key_command_from_name(buf
);
182 if(cmd
== CMD_NONE
) {
183 /* the hotkey configuration contains an unknown
185 print_error(_("Unknown command"), buf
);
189 /* skip whitespace */
190 while (i
< len
&& (str
[i
] == '=' || g_ascii_isspace(str
[i
])))
193 /* get the value part */
194 memset(buf
, 0, MAX_LINE_LENGTH
);
195 g_strlcpy(buf
, str
+i
, MAX_LINE_LENGTH
);
197 /* the hotkey configuration line is incomplete */
198 print_error(_("Incomplete hotkey configuration"), str
);
202 /* parse key values */
207 int keys
[MAX_COMMAND_KEYS
];
208 memset(keys
, 0, sizeof(int)*MAX_COMMAND_KEYS
);
209 while (i
< MAX_COMMAND_KEYS
&& *p
!= 0 &&
210 (key
= parse_key_value(p
, &p
)) >= 0) {
212 while (*p
==',' || *p
==' ' || *p
=='\t')
219 return assign_keys(cmd
, keys
);
223 parse_timedisplay_type(const char *str
)
225 if (strcmp(str
, "elapsed") == 0)
227 else if (strcmp(str
, "remaining") == 0)
230 /* translators: ncmpc supports displaying the
231 "elapsed" or "remaining" time of a song being
232 played; in this case, the configuration file
233 contained an invalid setting */
234 print_error(_("Bad time display type"), str
);
241 separate_value(char *p
)
243 char *value
= strchr(p
, '=');
245 /* an equals sign '=' was expected while parsing a
246 configuration file line */
247 fprintf(stderr
, "%s\n", _("Missing '='"));
254 return skip_spaces(value
);
258 parse_color(char *str
)
260 char *value
= separate_value(str
);
264 return colors_assign(str
, value
);
268 * Returns the first non-whitespace character after the next comma
269 * character, or the end of the string. This is used to parse comma
275 char *comma
= strchr(p
, ',');
279 comma
= skip_spaces(comma
);
281 comma
= p
+ strlen(p
);
288 parse_color_definition(char *str
)
290 char *value
= separate_value(str
);
294 /* get the command name */
295 short color
= colors_str2color(str
);
297 char buf
[MAX_LINE_LENGTH
];
298 print_error(_("Bad color name"), buf
);
302 /* parse r,g,b values */
305 for (unsigned i
= 0; i
< 3; ++i
) {
306 char *next
= after_comma(value
), *endptr
;
308 print_error(_("Incomplete color definition"), str
);
312 rgb
[i
] = strtol(value
, &endptr
, 0);
313 if (endptr
== value
|| *endptr
!= 0) {
314 print_error(_("Invalid number"), value
);
322 print_error(_("Malformed color definition"), str
);
326 return colors_define(str
, rgb
[0], rgb
[1], rgb
[2]);
331 get_format(char *str
)
333 gsize len
= strlen(str
);
335 if (str
&& str
[0]=='\"' && str
[len
-1] == '\"') {
340 return g_strdup(str
);
344 check_screen_list(char *value
)
346 char **tmp
= g_strsplit_set(value
, " \t,", 100);
347 char **screen
= NULL
;
350 while( tmp
&& tmp
[i
] ) {
351 char *name
= g_ascii_strdown(tmp
[i
], -1);
353 if (screen_lookup_name(name
) == NULL
) {
354 /* an unknown screen name was specified in the
355 configuration file */
356 print_error(_("Unknown screen name"), name
);
359 screen
= g_realloc(screen
, (j
+2)*sizeof(char *));
368 return g_strsplit_set(DEFAULT_SCREEN_LIST
, " ", 0);
374 get_search_mode(char *value
)
377 const int mode
= strtol(value
, &test
, 10);
380 if (0 <= mode
&& mode
<= 4)
384 print_error(_("Invalid search mode"),value
);
390 for (int i
= 0; value
[i
] != '\0'; i
++)
391 value
[i
] = tolower(value
[i
]);
393 // TODO: modify screen_search so that its own list of modes can be used
394 // for comparison instead of specifying them here
395 if (strcmp(value
, "title") == 0)
397 else if (strcmp(value
, "artist") == 0)
399 else if (strcmp(value
, "album") == 0)
401 else if (strcmp(value
, "filename") == 0)
403 else if (strcmp(value
, "artist+album") == 0)
407 print_error(_("Unknown search mode"),value
);
414 parse_line(char *line
)
416 /* get the name part */
417 char *const name
= line
;
418 char *const name_end
= after_unquoted_word(line
);
419 if (name_end
== NULL
)
422 line
= skip_spaces(name_end
);
425 line
= skip_spaces(line
);
426 } else if (line
== name_end
) {
427 print_error(_("Missing '='"), name_end
);
433 /* get the value part */
434 char *const value
= line
;
437 if (!strcasecmp(CONF_KEY_DEFINITION
, name
))
438 parse_key_definition(value
);
440 else if(!strcasecmp(CONF_ENABLE_COLORS
, name
))
442 options
.enable_colors
= str2bool(value
);
446 else if (!strcasecmp(CONF_SCROLL_OFFSET
, name
))
447 options
.scroll_offset
= atoi(value
);
449 else if (!strcasecmp(CONF_AUTO_CENTER
, name
))
450 options
.auto_center
= str2bool(value
);
451 /* color assignment */
452 else if (!strcasecmp(CONF_COLOR
, name
))
459 else if (!strcasecmp(CONF_WIDE_CURSOR
, name
))
460 options
.wide_cursor
= str2bool(value
);
461 else if (strcasecmp(name
, CONF_HARDWARE_CURSOR
) == 0)
462 options
.hardware_cursor
= str2bool(value
);
463 /* welcome screen list */
464 else if (!strcasecmp(CONF_WELCOME_SCREEN_LIST
, name
))
465 options
.welcome_screen_list
= str2bool(value
);
466 /* visible bitrate */
467 else if (!strcasecmp(CONF_VISIBLE_BITRATE
, name
))
468 options
.visible_bitrate
= str2bool(value
);
469 /* timer display type */
470 else if (!strcasecmp(CONF_TIMEDISPLAY_TYPE
, name
))
471 options
.display_remaining_time
= parse_timedisplay_type(value
);
472 /* color definition */
473 else if (!strcasecmp(CONF_COLOR_DEFINITION
, name
))
475 parse_color_definition(value
);
479 /* list format string */
480 else if (!strcasecmp(CONF_LIST_FORMAT
, name
)) {
481 g_free(options
.list_format
);
482 options
.list_format
= get_format(value
);
483 /* search format string */
484 } else if (!strcasecmp(CONF_SEARCH_FORMAT
, name
)) {
485 g_free(options
.search_format
);
486 options
.search_format
= get_format(value
);
487 /* status format string */
488 } else if (!strcasecmp(CONF_STATUS_FORMAT
, name
)) {
489 g_free(options
.status_format
);
490 options
.status_format
= get_format(value
);
491 /* xterm title format string */
492 } else if (!strcasecmp(CONF_XTERM_TITLE_FORMAT
, name
)) {
493 g_free(options
.xterm_title_format
);
494 options
.xterm_title_format
= get_format(value
);
495 } else if (!strcasecmp(CONF_LIST_WRAP
, name
))
496 options
.list_wrap
= str2bool(value
);
497 else if (!strcasecmp(CONF_FIND_WRAP
, name
))
498 options
.find_wrap
= str2bool(value
);
499 else if (!strcasecmp(CONF_FIND_SHOW_LAST
,name
))
500 options
.find_show_last_pattern
= str2bool(value
);
501 else if (!strcasecmp(CONF_AUDIBLE_BELL
, name
))
502 options
.audible_bell
= str2bool(value
);
503 else if (!strcasecmp(CONF_VISIBLE_BELL
, name
))
504 options
.visible_bell
= str2bool(value
);
505 else if (!strcasecmp(CONF_BELL_ON_WRAP
, name
))
506 options
.bell_on_wrap
= str2bool(value
);
507 else if (!strcasecmp(CONF_STATUS_MESSAGE_TIME
, name
))
508 options
.status_message_time
= atoi(value
);
509 else if (!strcasecmp(CONF_XTERM_TITLE
, name
))
510 options
.enable_xterm_title
= str2bool(value
);
511 else if (!strcasecmp(CONF_ENABLE_MOUSE
, name
))
513 options
.enable_mouse
= str2bool(value
);
517 else if (!strcasecmp(CONF_CROSSFADE_TIME
, name
))
518 options
.crossfade_time
= atoi(value
);
519 else if (!strcasecmp(CONF_SEARCH_MODE
, name
))
520 options
.search_mode
= get_search_mode(value
);
521 else if (!strcasecmp(CONF_HIDE_CURSOR
, name
))
522 options
.hide_cursor
= atoi(value
);
523 else if (!strcasecmp(CONF_SEEK_TIME
, name
))
524 options
.seek_time
= atoi(value
);
525 else if (!strcasecmp(CONF_SCREEN_LIST
, name
)) {
526 g_strfreev(options
.screen_list
);
527 options
.screen_list
= check_screen_list(value
);
528 } else if (!strcasecmp(CONF_HOST
, name
))
529 options
.host
= get_format(value
);
530 else if (!strcasecmp(CONF_PORT
, name
))
531 options
.port
= atoi(get_format(value
));
532 else if (!strcasecmp(CONF_PASSWORD
, name
))
533 options
.password
= get_format(value
);
534 else if (!strcasecmp(CONF_TIMEOUT
, name
))
535 options
.timeout_ms
= atoi(get_format(value
))
536 * 1000 /* seconds -> milliseconds */;
537 else if (!strcasecmp(CONF_LYRICS_TIMEOUT
, name
))
538 #ifdef ENABLE_LYRICS_SCREEN
539 options
.lyrics_timeout
= atoi(get_format(value
));
543 else if (!strcasecmp(CONF_SCROLL
, name
))
544 options
.scroll
= str2bool(value
);
545 else if (!strcasecmp(CONF_SCROLL_SEP
, name
)) {
546 g_free(options
.scroll_sep
);
547 options
.scroll_sep
= get_format(value
);
548 } else if (!strcasecmp(CONF_DISPLAY_TIME
, name
))
549 /* obsolete, ignore */
551 else if (!strcasecmp(CONF_JUMP_PREFIX_ONLY
, name
))
555 options
.jump_prefix_only
= str2bool(value
);
557 else if (!strcasecmp(CONF_LYRICS_AUTOSAVE
, name
))
558 #ifdef ENABLE_LYRICS_SCREEN
559 options
.lyrics_autosave
= str2bool(value
);
563 else if (!strcasecmp(CONF_LYRICS_SHOW_PLUGIN
, name
))
564 #ifdef ENABLE_LYRICS_SCREEN
565 options
.lyrics_show_plugin
= str2bool(value
);
569 else if (!strcasecmp(name
, CONF_TEXT_EDITOR
))
570 #ifdef ENABLE_LYRICS_SCREEN
572 g_free(options
.text_editor
);
573 options
.text_editor
= get_format(value
);
578 else if (!strcasecmp(name
, CONF_TEXT_EDITOR_ASK
))
579 #ifdef ENABLE_LYRICS_SCREEN
580 options
.text_editor_ask
= str2bool(value
);
584 else if (!strcasecmp(name
, CONF_CHAT_PREFIX
))
585 #ifdef ENABLE_CHAT_SCREEN
586 options
.chat_prefix
= get_format(value
);
590 else if (!strcasecmp(CONF_SECOND_COLUMN
, name
))
594 options
.second_column
= str2bool(value
);
597 print_error(_("Unknown configuration parameter"), name
);
605 read_rc_file(char *filename
)
607 assert(filename
!= NULL
);
609 FILE *file
= fopen(filename
, "r");
615 char line
[MAX_LINE_LENGTH
];
616 while (fgets(line
, sizeof(line
), file
) != NULL
) {
617 char *p
= skip_spaces(line
);
619 if (*p
!= 0 && *p
!= COMMENT_TOKEN
)
620 parse_line(g_strchomp(p
));
628 check_user_conf_dir(void)
630 char *directory
= g_build_filename(g_get_home_dir(), "." PACKAGE
, NULL
);
632 if (g_file_test(directory
, G_FILE_TEST_IS_DIR
)) {
637 bool success
= g_mkdir(directory
, 0755) == 0;
643 build_user_conf_filename(void)
646 return g_build_filename(g_get_user_config_dir(), PACKAGE
, "ncmpc.conf", NULL
);
648 return g_build_filename(g_get_home_dir(), "." PACKAGE
, "config", NULL
);
653 build_system_conf_filename(void)
656 const gchar
* const *system_data_dirs
;
657 gchar
*pathname
= NULL
;
659 for (system_data_dirs
= g_get_system_config_dirs (); *system_data_dirs
!= NULL
; system_data_dirs
++)
661 pathname
= g_build_filename(*system_data_dirs
, PACKAGE
, "ncmpc.conf", NULL
);
662 if (g_file_test(pathname
, G_FILE_TEST_EXISTS
))
674 return g_build_filename(SYSCONFDIR
, PACKAGE
, "config", NULL
);
679 build_user_key_binding_filename(void)
682 return g_build_filename(g_get_user_config_dir(), PACKAGE
, "keys.conf", NULL
);
684 return g_build_filename(g_get_home_dir(), "." PACKAGE
, "keys", NULL
);
689 g_build_system_key_binding_filename(void)
692 const gchar
* const *system_data_dirs
;
693 gchar
*pathname
= NULL
;
695 for (system_data_dirs
= g_get_system_config_dirs (); *system_data_dirs
!= NULL
; system_data_dirs
++)
697 pathname
= g_build_filename(*system_data_dirs
, PACKAGE
, "keys.conf", NULL
);
698 if (g_file_test(pathname
, G_FILE_TEST_EXISTS
))
710 return g_build_filename(SYSCONFDIR
, PACKAGE
, "keys", NULL
);
715 find_config_file(void)
717 /* check for command line configuration file */
718 if (options
.config_file
!= NULL
)
719 return g_strdup(options
.config_file
);
721 /* check for user configuration ~/.ncmpc/config */
722 char *filename
= build_user_conf_filename();
723 if (g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
728 /* check for global configuration SYSCONFDIR/ncmpc/config */
729 filename
= build_system_conf_filename();
730 if (g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
740 /* check for command line key binding file */
741 if (options
.key_file
!= NULL
)
742 return g_strdup(options
.key_file
);
744 /* check for user key bindings ~/.ncmpc/keys */
745 char *filename
= build_user_key_binding_filename();
746 if (g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
751 /* check for global key bindings SYSCONFDIR/ncmpc/keys */
752 filename
= g_build_system_key_binding_filename();
753 if (g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
761 read_configuration(void)
763 /* load configuration */
764 char *filename
= find_config_file();
765 if (filename
!= NULL
) {
766 read_rc_file(filename
);
770 /* load key bindings */
771 filename
= find_keys_file();
772 if (filename
!= NULL
) {
773 read_rc_file(filename
);