2102 lines
46 KiB
C
2102 lines
46 KiB
C
/*
|
|
* Copyright 2008-2013 Various Authors
|
|
* Copyright 2006 Timo Hirvonen
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "options.h"
|
|
#include "list.h"
|
|
#include "utils.h"
|
|
#include "xmalloc.h"
|
|
#include "player.h"
|
|
#include "buffer.h"
|
|
#include "ui_curses.h"
|
|
#include "cmus.h"
|
|
#include "misc.h"
|
|
#include "lib.h"
|
|
#include "pl.h"
|
|
#include "browser.h"
|
|
#include "keys.h"
|
|
#include "filters.h"
|
|
#include "command_mode.h"
|
|
#include "file.h"
|
|
#include "prog.h"
|
|
#include "output.h"
|
|
#include "input.h"
|
|
#include "xstrjoin.h"
|
|
#include "track_info.h"
|
|
#include "cache.h"
|
|
#include "debug.h"
|
|
#include "discid.h"
|
|
#include "mpris.h"
|
|
#ifdef HAVE_CONFIG
|
|
#include "config/curses.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <strings.h>
|
|
|
|
#if defined(__sun__)
|
|
#include <ncurses.h>
|
|
#else
|
|
#include <curses.h>
|
|
#endif
|
|
|
|
/* initialized option variables */
|
|
|
|
char *cdda_device = NULL;
|
|
char *output_plugin = NULL;
|
|
char *status_display_program = NULL;
|
|
char *server_password;
|
|
int auto_reshuffle = 1;
|
|
int confirm_run = 1;
|
|
int resume_cmus = 0;
|
|
int show_hidden = 0;
|
|
int show_current_bitrate = 0;
|
|
int show_playback_position = 1;
|
|
int show_remaining_time = 0;
|
|
int set_term_title = 1;
|
|
int wrap_search = 1;
|
|
int play_library = 1;
|
|
int repeat = 0;
|
|
int shuffle = 0;
|
|
int follow = 0;
|
|
int display_artist_sort_name;
|
|
int smart_artist_sort = 1;
|
|
int sort_albums_by_name = 0;
|
|
int scroll_offset = 2;
|
|
int rewind_offset = 5;
|
|
int skip_track_info = 0;
|
|
int ignore_duplicates = 0;
|
|
int auto_expand_albums_follow = 1;
|
|
int auto_expand_albums_search = 1;
|
|
int auto_expand_albums_selcur = 1;
|
|
int auto_hide_playlists_panel = 0;
|
|
int show_all_tracks = 1;
|
|
int mouse = 0;
|
|
int mpris = 1;
|
|
int time_show_leading_zero = 1;
|
|
int start_view = TREE_VIEW;
|
|
int stop_after_queue = 0;
|
|
int tree_width_percent = 33;
|
|
int tree_width_max = 0;
|
|
int pause_on_output_change = 0;
|
|
int block_key_paste = 1;
|
|
int progress_bar = 1;
|
|
int search_resets_position = 1;
|
|
|
|
int colors[NR_COLORS] = {
|
|
-1,
|
|
-1,
|
|
COLOR_RED | BRIGHT,
|
|
COLOR_YELLOW | BRIGHT,
|
|
|
|
COLOR_BLUE,
|
|
COLOR_WHITE,
|
|
COLOR_BLACK,
|
|
COLOR_BLUE,
|
|
|
|
COLOR_WHITE | BRIGHT,
|
|
COLOR_BLUE,
|
|
COLOR_WHITE | BRIGHT,
|
|
-1,
|
|
|
|
COLOR_YELLOW | BRIGHT,
|
|
COLOR_BLUE,
|
|
COLOR_YELLOW | BRIGHT,
|
|
COLOR_BLUE | BRIGHT,
|
|
|
|
-1,
|
|
COLOR_WHITE,
|
|
COLOR_YELLOW | BRIGHT,
|
|
COLOR_WHITE,
|
|
|
|
COLOR_BLACK,
|
|
COLOR_BLUE,
|
|
COLOR_WHITE | BRIGHT,
|
|
COLOR_BLUE,
|
|
|
|
COLOR_WHITE | BRIGHT,
|
|
-1,
|
|
-1,
|
|
};
|
|
|
|
int attrs[NR_ATTRS] = {
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_NORMAL,
|
|
A_BOLD,
|
|
A_NORMAL,
|
|
};
|
|
|
|
/* uninitialized option variables */
|
|
char *tree_win_format = NULL;
|
|
char *tree_win_artist_format = NULL;
|
|
char *track_win_album_format = NULL;
|
|
char *track_win_format = NULL;
|
|
char *track_win_format_va = NULL;
|
|
char *track_win_alt_format = NULL;
|
|
char *list_win_format = NULL;
|
|
char *list_win_format_va = NULL;
|
|
char *list_win_alt_format = NULL;
|
|
char *clipped_text_format = NULL;
|
|
char *clipped_text_internal = NULL;
|
|
char *current_format = NULL;
|
|
char *current_alt_format = NULL;
|
|
char *heading_album_format = NULL;
|
|
char *heading_artist_format = NULL;
|
|
char *heading_playlist_format = NULL;
|
|
char *statusline_format = NULL;
|
|
char *window_title_format = NULL;
|
|
char *window_title_alt_format = NULL;
|
|
char *id3_default_charset = NULL;
|
|
char *icecast_default_charset = NULL;
|
|
char *lib_add_filter = NULL;
|
|
char **pl_env_vars;
|
|
|
|
static void buf_int(char *buf, int val, size_t size)
|
|
{
|
|
snprintf(buf, size, "%d", val);
|
|
}
|
|
|
|
static int parse_int(const char *buf, int minval, int maxval, int *val)
|
|
{
|
|
long int tmp;
|
|
|
|
if (str_to_int(buf, &tmp) == -1 || tmp < minval || tmp > maxval) {
|
|
error_msg("integer in range %d..%d expected", minval, maxval);
|
|
return 0;
|
|
}
|
|
*val = tmp;
|
|
return 1;
|
|
}
|
|
|
|
int parse_enum(const char *buf, int minval, int maxval, const char * const names[], int *val)
|
|
{
|
|
long int tmp;
|
|
int i;
|
|
GBUF(names_buf);
|
|
|
|
if (str_to_int(buf, &tmp) == 0) {
|
|
if (tmp < minval || tmp > maxval)
|
|
goto err;
|
|
*val = tmp;
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; names[i]; i++) {
|
|
if (strcasecmp(buf, names[i]) == 0) {
|
|
*val = i + minval;
|
|
return 1;
|
|
}
|
|
}
|
|
err:
|
|
for (i = 0; names[i]; i++) {
|
|
if (i)
|
|
gbuf_add_str(&names_buf, ", ");
|
|
gbuf_add_str(&names_buf, names[i]);
|
|
}
|
|
error_msg("expected [%d..%d] or [%s]", minval, maxval, names_buf.buffer);
|
|
gbuf_free(&names_buf);
|
|
return 0;
|
|
}
|
|
|
|
static const char * const bool_names[] = {
|
|
"false", "true", NULL
|
|
};
|
|
|
|
static int parse_bool(const char *buf, int *val)
|
|
{
|
|
return parse_enum(buf, 0, 1, bool_names, val);
|
|
}
|
|
|
|
/* this is used as id in struct cmus_opt */
|
|
enum format_id {
|
|
FMT_CLIPPED_TEXT,
|
|
FMT_CURRENT_ALT,
|
|
FMT_CURRENT,
|
|
FMT_HEADING_ALBUM,
|
|
FMT_HEADING_ARTIST,
|
|
FMT_HEADING_PLAYLIST,
|
|
FMT_STATUSLINE,
|
|
FMT_PLAYLIST,
|
|
FMT_PLAYLIST_ALT,
|
|
FMT_PLAYLIST_VA,
|
|
FMT_TITLE,
|
|
FMT_TITLE_ALT,
|
|
FMT_TRACKWIN,
|
|
FMT_TRACKWIN_ALBUM,
|
|
FMT_TRACKWIN_ALT,
|
|
FMT_TRACKWIN_VA,
|
|
FMT_TREEWIN,
|
|
FMT_TREEWIN_ARTIST,
|
|
|
|
NR_FMTS
|
|
};
|
|
|
|
/* default values for the variables which we must initialize but
|
|
* can't do it statically */
|
|
static const struct {
|
|
const char *name;
|
|
const char *value;
|
|
} str_defaults[] = {
|
|
[FMT_CLIPPED_TEXT] = { "format_clipped_text" , "…" },
|
|
[FMT_CURRENT_ALT] = { "altformat_current" , " %F " },
|
|
[FMT_CURRENT] = { "format_current" , " %a - %l%! - %n. %t%= %y " },
|
|
[FMT_HEADING_ALBUM] = { "format_heading_album" , "%a - %l%= %y %{duration}" },
|
|
[FMT_HEADING_ARTIST] = { "format_heading_artist" , "%a%= %{duration}" },
|
|
[FMT_HEADING_PLAYLIST] = { "format_heading_playlist" , "%{?!panel?Playlist - }%{title}%= %{duration} " },
|
|
[FMT_STATUSLINE] = { "format_statusline" ,
|
|
" %{status} %{?show_playback_position?%{position} %{?duration?/ %{duration} }?%{?duration?%{duration} }}"
|
|
"%{?bpm>0?at %{bpm} BPM }"
|
|
"%{?stream?buf: %{buffer} }"
|
|
"%{?show_current_bitrate & bitrate>=0? %{bitrate} kbps }"
|
|
"%= "
|
|
"%{?repeat_current?repeat current?%{?play_library?%{?playlist_mode!=\"all\"?%{playlist_mode} from }%{?play_sorted?sorted }library?playlist}} | "
|
|
"%{?volume>=0?%{?lvolume!=rvolume?%{lvolume}%% %{rvolume}?%{volume}}%% | }"
|
|
"%1{continue}%1{follow}%1{repeat}%1{shuffle} "
|
|
},
|
|
[FMT_PLAYLIST_ALT] = { "altformat_playlist" , " %f%= %d %{?X!=0?%3X ? }" },
|
|
[FMT_PLAYLIST] = { "format_playlist" , " %-21%a %3n. %t%= %y %d %{?X!=0?%3X ? }" },
|
|
[FMT_PLAYLIST_VA] = { "format_playlist_va" , " %-21%A %3n. %t (%a)%= %y %d %{?X!=0?%3X ? }" },
|
|
[FMT_TITLE_ALT] = { "altformat_title" , "%f" },
|
|
[FMT_TITLE] = { "format_title" , "%a - %l - %t (%y)" },
|
|
[FMT_TRACKWIN_ALBUM] = { "format_trackwin_album" , " %l %= %y %{duration} " },
|
|
[FMT_TRACKWIN_ALT] = { "altformat_trackwin" , " %f%= %d " },
|
|
[FMT_TRACKWIN] = { "format_trackwin" , "%3n. %t%= %d " },
|
|
[FMT_TRACKWIN_VA] = { "format_trackwin_va" , "%3n. %t (%a)%= %d " },
|
|
[FMT_TREEWIN] = { "format_treewin" , " %l" },
|
|
[FMT_TREEWIN_ARTIST] = { "format_treewin_artist" , "%a" },
|
|
|
|
[NR_FMTS] =
|
|
|
|
{ "lib_sort", "albumartist date album discnumber tracknumber title filename play_count" },
|
|
{ "pl_sort", "" },
|
|
{ "id3_default_charset", "ISO-8859-1" },
|
|
{ "icecast_default_charset", "ISO-8859-1" },
|
|
{ "pl_env_vars", "" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int seen_pr1317_fmts = 0;
|
|
|
|
/* callbacks for normal options {{{ */
|
|
|
|
static void get_device(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, cdda_device, size);
|
|
}
|
|
|
|
static void set_device(void *data, const char *buf)
|
|
{
|
|
free(cdda_device);
|
|
cdda_device = expand_filename(buf);
|
|
}
|
|
|
|
#define SECOND_SIZE (44100 * 16 / 8 * 2)
|
|
static void get_buffer_seconds(void *data, char *buf, size_t size)
|
|
{
|
|
int val = (player_get_buffer_chunks() * CHUNK_SIZE + SECOND_SIZE / 2) /
|
|
SECOND_SIZE;
|
|
buf_int(buf, val, size);
|
|
}
|
|
|
|
static void set_buffer_seconds(void *data, const char *buf)
|
|
{
|
|
int sec;
|
|
|
|
if (parse_int(buf, 1, 300, &sec))
|
|
player_set_buffer_chunks((sec * SECOND_SIZE + CHUNK_SIZE / 2) / CHUNK_SIZE);
|
|
}
|
|
|
|
static void get_scroll_offset(void *data, char *buf, size_t size)
|
|
{
|
|
buf_int(buf, scroll_offset, size);
|
|
}
|
|
|
|
static void set_scroll_offset(void *data, const char *buf)
|
|
{
|
|
int offset;
|
|
|
|
if (parse_int(buf, 0, 9999, &offset))
|
|
scroll_offset = offset;
|
|
}
|
|
|
|
static void get_rewind_offset(void *data, char *buf, size_t size)
|
|
{
|
|
buf_int(buf, rewind_offset, size);
|
|
}
|
|
|
|
static void set_rewind_offset(void *data, const char *buf)
|
|
{
|
|
int offset;
|
|
|
|
if (parse_int(buf, -1, 9999, &offset))
|
|
rewind_offset = offset;
|
|
}
|
|
|
|
static void get_id3_default_charset(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, id3_default_charset, size);
|
|
}
|
|
|
|
static void get_icecast_default_charset(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, icecast_default_charset, size);
|
|
}
|
|
|
|
static void set_id3_default_charset(void *data, const char *buf)
|
|
{
|
|
free(id3_default_charset);
|
|
id3_default_charset = xstrdup(buf);
|
|
}
|
|
|
|
static void set_icecast_default_charset(void *data, const char *buf)
|
|
{
|
|
free(icecast_default_charset);
|
|
icecast_default_charset = xstrdup(buf);
|
|
}
|
|
|
|
static void get_lib_sort(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, lib_editable.shared->sort_str, size);
|
|
}
|
|
|
|
static void set_lib_sort(void *data, const char *buf)
|
|
{
|
|
sort_key_t *keys = parse_sort_keys(buf);
|
|
|
|
if (keys) {
|
|
editable_shared_set_sort_keys(lib_editable.shared, keys);
|
|
editable_sort(&lib_editable);
|
|
sort_keys_to_str(keys, lib_editable.shared->sort_str,
|
|
sizeof(lib_editable.shared->sort_str));
|
|
}
|
|
}
|
|
|
|
static void get_pl_sort(void *data, char *buf, size_t size)
|
|
{
|
|
pl_get_sort_str(buf, size);
|
|
}
|
|
|
|
static void set_pl_sort(void *data, const char *buf)
|
|
{
|
|
pl_set_sort_str(buf);
|
|
}
|
|
|
|
static void get_output_plugin(void *data, char *buf, size_t size)
|
|
{
|
|
const char *value = op_get_current();
|
|
|
|
if (value)
|
|
strscpy(buf, value, size);
|
|
}
|
|
|
|
static void set_output_plugin(void *data, const char *buf)
|
|
{
|
|
if (ui_initialized) {
|
|
if (!soft_vol || pause_on_output_change)
|
|
mixer_close();
|
|
player_set_op(buf);
|
|
if (!soft_vol || pause_on_output_change)
|
|
mixer_open();
|
|
} else {
|
|
/* must set it later manually */
|
|
output_plugin = xstrdup(buf);
|
|
}
|
|
}
|
|
|
|
static void get_passwd(void *data, char *buf, size_t size)
|
|
{
|
|
if (server_password)
|
|
strscpy(buf, server_password, size);
|
|
}
|
|
|
|
static void set_passwd(void *data, const char *buf)
|
|
{
|
|
int len = strlen(buf);
|
|
|
|
if (len == 0) {
|
|
free(server_password);
|
|
server_password = NULL;
|
|
} else if (len < 6) {
|
|
error_msg("unsafe password");
|
|
} else {
|
|
free(server_password);
|
|
server_password = xstrdup(buf);
|
|
}
|
|
}
|
|
|
|
static void get_replaygain_preamp(void *data, char *buf, size_t size)
|
|
{
|
|
snprintf(buf, size, "%f", replaygain_preamp);
|
|
}
|
|
|
|
static void set_replaygain_preamp(void *data, const char *buf)
|
|
{
|
|
double val;
|
|
char *end;
|
|
|
|
val = strtod(buf, &end);
|
|
if (end == buf) {
|
|
error_msg("floating point number expected (dB)");
|
|
return;
|
|
}
|
|
player_set_rg_preamp(val);
|
|
}
|
|
|
|
static void get_softvol_state(void *data, char *buf, size_t size)
|
|
{
|
|
snprintf(buf, size, "%d %d", soft_vol_l, soft_vol_r);
|
|
}
|
|
|
|
static void set_softvol_state(void *data, const char *buf)
|
|
{
|
|
char buffer[OPTION_MAX_SIZE];
|
|
char *ptr;
|
|
long int l, r;
|
|
|
|
strscpy(buffer, buf, sizeof(buffer));
|
|
ptr = strchr(buffer, ' ');
|
|
if (!ptr)
|
|
goto err;
|
|
while (*ptr == ' ')
|
|
*ptr++ = 0;
|
|
|
|
if (str_to_int(buffer, &l) == -1 || l < 0 || l > 100)
|
|
goto err;
|
|
if (str_to_int(ptr, &r) == -1 || r < 0 || r > 100)
|
|
goto err;
|
|
|
|
player_set_soft_volume(l, r);
|
|
return;
|
|
err:
|
|
error_msg("two integers in range 0..100 expected");
|
|
}
|
|
|
|
static void get_status_display_program(void *data, char *buf, size_t size)
|
|
{
|
|
if (status_display_program)
|
|
strscpy(buf, status_display_program, size);
|
|
}
|
|
|
|
static void set_status_display_program(void *data, const char *buf)
|
|
{
|
|
free(status_display_program);
|
|
status_display_program = NULL;
|
|
if (buf[0])
|
|
status_display_program = expand_filename(buf);
|
|
}
|
|
|
|
static void get_tree_width_percent(void *data, char *buf, size_t size)
|
|
{
|
|
buf_int(buf, tree_width_percent, size);
|
|
}
|
|
|
|
static void set_tree_width_percent(void *data, const char *buf)
|
|
{
|
|
int percent;
|
|
|
|
if (parse_int(buf, 1, 100, &percent))
|
|
tree_width_percent = percent;
|
|
update_size();
|
|
}
|
|
|
|
static void get_tree_width_max(void *data, char *buf, size_t size)
|
|
{
|
|
buf_int(buf, tree_width_max, size);
|
|
}
|
|
|
|
static void set_tree_width_max(void *data, const char *buf)
|
|
{
|
|
int cols;
|
|
|
|
if (parse_int(buf, 0, 9999, &cols))
|
|
tree_width_max = cols;
|
|
update_size();
|
|
}
|
|
|
|
/* get_pl_env_vars converts the pl_env_vars array into a comma-separated list */
|
|
static void get_pl_env_vars(void *data, char *buf, size_t size)
|
|
{
|
|
if (!pl_env_vars) {
|
|
strscpy(buf, "", size);
|
|
return;
|
|
}
|
|
char *p = buf;
|
|
size_t r = size - 1;
|
|
for (char **x = pl_env_vars; *x; x++) {
|
|
if (x != pl_env_vars) {
|
|
if (!--r)
|
|
return; /* overflow */
|
|
*p++ = ',';
|
|
}
|
|
size_t l = strlen(*x);
|
|
if (!(r -= l))
|
|
return; /* overflow */
|
|
strcpy(p, *x);
|
|
p += l;
|
|
}
|
|
*p = '\0';
|
|
}
|
|
|
|
/* set_pl_env_vars splits the pl_env_vars config into the pl_env_vars array */
|
|
static void set_pl_env_vars(void *data, const char *buf)
|
|
{
|
|
if (pl_env_vars) {
|
|
free(*pl_env_vars);
|
|
free(pl_env_vars);
|
|
}
|
|
if (!*buf) {
|
|
pl_env_vars = NULL;
|
|
}
|
|
size_t n = 1;
|
|
for (const char *x = buf; *x; x++) {
|
|
if (*x == ',') {
|
|
n++;
|
|
}
|
|
}
|
|
char **a = pl_env_vars = xnew(char*, n+1);
|
|
for (char *x = *a++ = xstrdup(buf); *x; x++) {
|
|
if (*x == ',') {
|
|
*a++ = x+1;
|
|
*x = '\0';
|
|
}
|
|
}
|
|
*a = NULL;
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* callbacks for toggle options {{{ */
|
|
|
|
static void get_auto_hide_playlists_panel(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[auto_hide_playlists_panel], size);
|
|
}
|
|
|
|
static void set_auto_hide_playlists_panel(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &auto_hide_playlists_panel);
|
|
}
|
|
|
|
static void toggle_auto_hide_playlists_panel(void *data)
|
|
{
|
|
auto_hide_playlists_panel ^= 1;
|
|
}
|
|
|
|
static void get_auto_reshuffle(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[auto_reshuffle], size);
|
|
}
|
|
|
|
static void set_auto_reshuffle(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &auto_reshuffle);
|
|
}
|
|
|
|
static void toggle_auto_reshuffle(void *data)
|
|
{
|
|
auto_reshuffle ^= 1;
|
|
}
|
|
|
|
static void get_follow(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[follow], size);
|
|
}
|
|
|
|
static void set_follow(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &follow))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_follow(void *data)
|
|
{
|
|
follow ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_continue(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[player_cont], size);
|
|
}
|
|
|
|
static void set_continue(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &player_cont))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_continue(void *data)
|
|
{
|
|
player_cont ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_continue_album(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[player_cont_album], size);
|
|
}
|
|
|
|
static void set_continue_album(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &player_cont_album))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_continue_album(void *data)
|
|
{
|
|
player_cont_album ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_repeat_current(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[player_repeat_current], size);
|
|
}
|
|
|
|
static void set_repeat_current(void *data, const char *buf)
|
|
{
|
|
int old = player_repeat_current;
|
|
if (!parse_bool(buf, &player_repeat_current))
|
|
return;
|
|
if (old != player_repeat_current)
|
|
mpris_loop_status_changed();
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_repeat_current(void *data)
|
|
{
|
|
player_repeat_current ^= 1;
|
|
mpris_loop_status_changed();
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_confirm_run(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[confirm_run], size);
|
|
}
|
|
|
|
static void set_confirm_run(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &confirm_run);
|
|
}
|
|
|
|
static void toggle_confirm_run(void *data)
|
|
{
|
|
confirm_run ^= 1;
|
|
}
|
|
|
|
const char * const view_names[NR_VIEWS + 1] = {
|
|
"tree", "sorted", "playlist", "queue", "browser", "filters", "settings", NULL
|
|
};
|
|
|
|
static void get_play_library(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[play_library], size);
|
|
}
|
|
|
|
static void set_play_library(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &play_library))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_play_library(void *data)
|
|
{
|
|
play_library ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_play_sorted(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[play_sorted], size);
|
|
}
|
|
|
|
static void set_play_sorted(void *data, const char *buf)
|
|
{
|
|
int tmp;
|
|
|
|
if (!parse_bool(buf, &tmp))
|
|
return;
|
|
|
|
play_sorted = tmp;
|
|
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_play_sorted(void *data)
|
|
{
|
|
play_sorted = play_sorted ^ 1;
|
|
|
|
if (play_sorted) {
|
|
/* play_sorted makes no sense in playlist */
|
|
play_library = 1;
|
|
}
|
|
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_smart_artist_sort(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[smart_artist_sort], size);
|
|
}
|
|
|
|
static void set_smart_artist_sort(void *data, const char *buf)
|
|
{
|
|
if (parse_bool(buf, &smart_artist_sort))
|
|
lib_sort_artists();
|
|
}
|
|
|
|
static void toggle_smart_artist_sort(void *data)
|
|
{
|
|
smart_artist_sort ^= 1;
|
|
lib_sort_artists();
|
|
}
|
|
|
|
static void get_sort_albums_by_name(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[sort_albums_by_name], size);
|
|
}
|
|
|
|
static void set_sort_albums_by_name(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &sort_albums_by_name);
|
|
lib_sort_artists();
|
|
}
|
|
|
|
static void toggle_sort_albums_by_name(void *data)
|
|
{
|
|
sort_albums_by_name ^= 1;
|
|
lib_sort_artists();
|
|
}
|
|
|
|
static void get_display_artist_sort_name(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[display_artist_sort_name], size);
|
|
}
|
|
|
|
static void set_display_artist_sort_name(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &display_artist_sort_name);
|
|
lib_tree_win->changed = 1;
|
|
}
|
|
|
|
static void toggle_display_artist_sort_name(void *data)
|
|
{
|
|
display_artist_sort_name ^= 1;
|
|
lib_tree_win->changed = 1;
|
|
}
|
|
|
|
const char * const aaa_mode_names[] = {
|
|
"all", "artist", "album", NULL
|
|
};
|
|
|
|
static void get_aaa_mode(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, aaa_mode_names[aaa_mode], size);
|
|
}
|
|
|
|
static void set_aaa_mode(void *data, const char *buf)
|
|
{
|
|
int tmp;
|
|
|
|
if (!parse_enum(buf, 0, 2, aaa_mode_names, &tmp))
|
|
return;
|
|
|
|
aaa_mode = tmp;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_aaa_mode(void *data)
|
|
{
|
|
/* aaa mode makes no sense in playlist */
|
|
play_library = 1;
|
|
|
|
aaa_mode++;
|
|
aaa_mode %= 3;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_repeat(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[repeat], size);
|
|
}
|
|
|
|
static void set_repeat(void *data, const char *buf)
|
|
{
|
|
int old = repeat;
|
|
if (!parse_bool(buf, &repeat))
|
|
return;
|
|
if (!player_repeat_current && old != repeat)
|
|
mpris_loop_status_changed();
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_repeat(void *data)
|
|
{
|
|
repeat ^= 1;
|
|
if (!player_repeat_current)
|
|
mpris_loop_status_changed();
|
|
update_statusline();
|
|
}
|
|
|
|
static const char * const replaygain_names[] = {
|
|
"disabled", "track", "album", "track-preferred", "album-preferred", "smart", NULL
|
|
};
|
|
|
|
static const size_t replaygain_names_len = sizeof(replaygain_names) / sizeof(replaygain_names[0]) - 1;
|
|
|
|
static void get_replaygain(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, replaygain_names[replaygain], size);
|
|
}
|
|
|
|
static void set_replaygain(void *data, const char *buf)
|
|
{
|
|
int tmp;
|
|
|
|
if (!parse_enum(buf, 0, replaygain_names_len - 1, replaygain_names, &tmp))
|
|
return;
|
|
player_set_rg(tmp);
|
|
}
|
|
|
|
static void toggle_replaygain(void *data)
|
|
{
|
|
player_set_rg((replaygain + 1) % replaygain_names_len);
|
|
}
|
|
|
|
static void get_replaygain_limit(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[replaygain_limit], size);
|
|
}
|
|
|
|
static void set_replaygain_limit(void *data, const char *buf)
|
|
{
|
|
int tmp;
|
|
|
|
if (!parse_bool(buf, &tmp))
|
|
return;
|
|
player_set_rg_limit(tmp);
|
|
}
|
|
|
|
static void toggle_replaygain_limit(void *data)
|
|
{
|
|
player_set_rg_limit(replaygain_limit ^ 1);
|
|
}
|
|
|
|
static void get_resume(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[resume_cmus], size);
|
|
}
|
|
|
|
static void set_resume(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &resume_cmus);
|
|
}
|
|
|
|
static void toggle_resume(void *data)
|
|
{
|
|
resume_cmus ^= 1;
|
|
}
|
|
|
|
static void get_show_hidden(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[show_hidden], size);
|
|
}
|
|
|
|
static void set_show_hidden(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &show_hidden))
|
|
return;
|
|
browser_reload();
|
|
}
|
|
|
|
static void toggle_show_hidden(void *data)
|
|
{
|
|
show_hidden ^= 1;
|
|
browser_reload();
|
|
}
|
|
|
|
static void set_show_all_tracks_int(int); /* defined below */
|
|
|
|
static void get_auto_expand_albums_follow(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[auto_expand_albums_follow], size);
|
|
}
|
|
|
|
static void set_auto_expand_albums_follow_int(int value)
|
|
{
|
|
auto_expand_albums_follow = !!value;
|
|
if (!auto_expand_albums_follow && !show_all_tracks)
|
|
set_show_all_tracks_int(1);
|
|
}
|
|
|
|
static void set_auto_expand_albums_follow(void *data, const char *buf)
|
|
{
|
|
int tmp = 0;
|
|
parse_bool(buf, &tmp);
|
|
set_auto_expand_albums_follow_int(tmp);
|
|
}
|
|
|
|
static void toggle_auto_expand_albums_follow(void *data)
|
|
{
|
|
set_auto_expand_albums_follow_int(!auto_expand_albums_follow);
|
|
}
|
|
|
|
static void get_auto_expand_albums_search(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[auto_expand_albums_search], size);
|
|
}
|
|
|
|
static void set_auto_expand_albums_search_int(int value)
|
|
{
|
|
auto_expand_albums_search = !!value;
|
|
if (!auto_expand_albums_search && !show_all_tracks)
|
|
set_show_all_tracks_int(1);
|
|
}
|
|
|
|
static void set_auto_expand_albums_search(void *data, const char *buf)
|
|
{
|
|
int tmp = 0;
|
|
parse_bool(buf, &tmp);
|
|
set_auto_expand_albums_search_int(tmp);
|
|
}
|
|
|
|
static void toggle_auto_expand_albums_search(void *data)
|
|
{
|
|
set_auto_expand_albums_search_int(!auto_expand_albums_search);
|
|
}
|
|
|
|
static void get_auto_expand_albums_selcur(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[auto_expand_albums_selcur], size);
|
|
}
|
|
|
|
static void set_auto_expand_albums_selcur_int(int value)
|
|
{
|
|
auto_expand_albums_selcur = !!value;
|
|
if (!auto_expand_albums_selcur && !show_all_tracks)
|
|
set_show_all_tracks_int(1);
|
|
}
|
|
|
|
static void set_auto_expand_albums_selcur(void *data, const char *buf)
|
|
{
|
|
int tmp = 0;
|
|
parse_bool(buf, &tmp);
|
|
set_auto_expand_albums_selcur_int(tmp);
|
|
}
|
|
|
|
static void toggle_auto_expand_albums_selcur(void *data)
|
|
{
|
|
set_auto_expand_albums_selcur_int(!auto_expand_albums_selcur);
|
|
}
|
|
|
|
|
|
static void get_show_all_tracks(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[show_all_tracks], size);
|
|
}
|
|
|
|
static void set_show_all_tracks_int(int value)
|
|
{
|
|
value = !!value;
|
|
if (show_all_tracks == value)
|
|
return;
|
|
show_all_tracks = value;
|
|
if (!show_all_tracks) {
|
|
if (!auto_expand_albums_follow)
|
|
set_auto_expand_albums_follow_int(1);
|
|
if (!auto_expand_albums_search)
|
|
set_auto_expand_albums_search_int(1);
|
|
if (!auto_expand_albums_selcur)
|
|
set_auto_expand_albums_selcur_int(1);
|
|
}
|
|
tree_sel_update(0);
|
|
}
|
|
|
|
static void set_show_all_tracks(void *data, const char *buf)
|
|
{
|
|
int tmp = 0;
|
|
parse_bool(buf, &tmp);
|
|
set_show_all_tracks_int(tmp);
|
|
}
|
|
|
|
static void toggle_show_all_tracks(void *data)
|
|
{
|
|
set_show_all_tracks_int(!show_all_tracks);
|
|
}
|
|
|
|
static void get_show_current_bitrate(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[show_current_bitrate], size);
|
|
}
|
|
|
|
static void set_show_current_bitrate(void *data, const char *buf)
|
|
{
|
|
if (parse_bool(buf, &show_current_bitrate))
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_show_current_bitrate(void *data)
|
|
{
|
|
show_current_bitrate ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_show_playback_position(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[show_playback_position], size);
|
|
}
|
|
|
|
static void set_show_playback_position(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &show_playback_position))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_show_playback_position(void *data)
|
|
{
|
|
show_playback_position ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_show_remaining_time(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[show_remaining_time], size);
|
|
}
|
|
|
|
static void set_show_remaining_time(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &show_remaining_time))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_show_remaining_time(void *data)
|
|
{
|
|
show_remaining_time ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_set_term_title(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[set_term_title], size);
|
|
}
|
|
|
|
static void set_set_term_title(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &set_term_title);
|
|
}
|
|
|
|
static void toggle_set_term_title(void *data)
|
|
{
|
|
set_term_title ^= 1;
|
|
}
|
|
|
|
const char * const shuffle_names[] = {
|
|
"off", "tracks", "albums", "false", "true", NULL
|
|
};
|
|
|
|
static void get_shuffle(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, shuffle_names[shuffle], size);
|
|
}
|
|
|
|
static void set_shuffle(void *data, const char *buf)
|
|
{
|
|
int tmp;
|
|
|
|
if (!parse_enum(buf, 0, 4, shuffle_names, &tmp))
|
|
return;
|
|
|
|
if (tmp == SHUFFLE_FALSE)
|
|
tmp = SHUFFLE_OFF;
|
|
else if (tmp == SHUFFLE_TRUE)
|
|
tmp = SHUFFLE_TRACKS;
|
|
|
|
if (tmp != shuffle)
|
|
mpris_shuffle_changed();
|
|
|
|
shuffle = tmp;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_shuffle(void *data)
|
|
{
|
|
shuffle++;
|
|
shuffle %= 3;
|
|
|
|
/* album mode makes no sense in playlist */
|
|
if (!play_library && shuffle == SHUFFLE_ALBUMS)
|
|
shuffle = SHUFFLE_OFF;
|
|
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_softvol(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[soft_vol], size);
|
|
}
|
|
|
|
static void do_set_softvol(int soft)
|
|
{
|
|
if (!soft_vol || pause_on_output_change)
|
|
mixer_close();
|
|
player_set_soft_vol(soft);
|
|
if (!soft_vol || pause_on_output_change)
|
|
mixer_open();
|
|
update_statusline();
|
|
}
|
|
|
|
static void set_softvol(void *data, const char *buf)
|
|
{
|
|
int soft;
|
|
|
|
if (!parse_bool(buf, &soft))
|
|
return;
|
|
do_set_softvol(soft);
|
|
}
|
|
|
|
static void toggle_softvol(void *data)
|
|
{
|
|
do_set_softvol(soft_vol ^ 1);
|
|
}
|
|
|
|
static void get_wrap_search(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[wrap_search], size);
|
|
}
|
|
|
|
static void set_wrap_search(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &wrap_search);
|
|
}
|
|
|
|
static void toggle_wrap_search(void *data)
|
|
{
|
|
wrap_search ^= 1;
|
|
}
|
|
|
|
static void get_skip_track_info(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[skip_track_info], size);
|
|
}
|
|
|
|
static void set_skip_track_info(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &skip_track_info);
|
|
}
|
|
|
|
static void toggle_skip_track_info(void *data)
|
|
{
|
|
skip_track_info ^= 1;
|
|
}
|
|
|
|
static void get_ignore_duplicates(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[ignore_duplicates], size);
|
|
}
|
|
|
|
static void set_ignore_duplicates(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &ignore_duplicates);
|
|
}
|
|
|
|
static void toggle_ignore_duplicates(void *data)
|
|
{
|
|
ignore_duplicates ^= 1;
|
|
}
|
|
|
|
void update_mouse(void)
|
|
{
|
|
if (mouse) {
|
|
mouseinterval(25);
|
|
mousemask(BUTTON_CTRL | BUTTON_ALT
|
|
| BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON1_CLICKED
|
|
| BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED
|
|
| BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON2_CLICKED
|
|
| BUTTON3_PRESSED | BUTTON3_RELEASED | BUTTON3_CLICKED
|
|
| BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED
|
|
| BUTTON4_PRESSED | BUTTON4_RELEASED | BUTTON4_CLICKED
|
|
| BUTTON4_DOUBLE_CLICKED | BUTTON4_TRIPLE_CLICKED
|
|
#if NCURSES_MOUSE_VERSION >= 2
|
|
| BUTTON5_PRESSED | BUTTON5_RELEASED | BUTTON5_CLICKED
|
|
| BUTTON5_DOUBLE_CLICKED | BUTTON5_TRIPLE_CLICKED
|
|
#endif
|
|
, NULL);
|
|
} else {
|
|
mousemask(0, NULL);
|
|
}
|
|
}
|
|
|
|
static void get_mouse(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[mouse], size);
|
|
}
|
|
|
|
static void set_mouse(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &mouse);
|
|
update_mouse();
|
|
}
|
|
|
|
static void toggle_mouse(void *data)
|
|
{
|
|
mouse ^= 1;
|
|
update_mouse();
|
|
}
|
|
|
|
static void get_mpris(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[mpris], size);
|
|
}
|
|
|
|
static void set_mpris(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &mpris);
|
|
}
|
|
|
|
static void toggle_mpris(void *data)
|
|
{
|
|
mpris ^= 1;
|
|
}
|
|
|
|
static void get_time_show_leading_zero(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[time_show_leading_zero], size);
|
|
}
|
|
|
|
static void set_time_show_leading_zero(void *data, const char *buf)
|
|
{
|
|
if (!parse_bool(buf, &time_show_leading_zero))
|
|
return;
|
|
update_statusline();
|
|
}
|
|
|
|
static void toggle_time_show_leading_zero(void *data)
|
|
{
|
|
time_show_leading_zero ^= 1;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_lib_add_filter(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, lib_add_filter ? lib_add_filter : "", size);
|
|
}
|
|
|
|
static void set_lib_add_filter(void *data, const char *buf)
|
|
{
|
|
struct expr *expr = NULL;
|
|
|
|
if (strlen(buf) != 0) {
|
|
/* parse expression if non-empty string given */
|
|
expr = expr_parse(buf);
|
|
|
|
if (!expr)
|
|
return;
|
|
}
|
|
|
|
free(lib_add_filter);
|
|
lib_add_filter = xstrdup(buf);
|
|
|
|
lib_set_add_filter(expr);
|
|
}
|
|
|
|
static void get_stop_after_queue(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[stop_after_queue], size);
|
|
}
|
|
|
|
static void set_stop_after_queue(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &stop_after_queue);
|
|
}
|
|
|
|
static void toggle_stop_after_queue(void *data)
|
|
{
|
|
stop_after_queue ^= 1;
|
|
}
|
|
|
|
static void get_pause_on_output_change(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[pause_on_output_change], size);
|
|
}
|
|
|
|
static void set_pause_on_output_change(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &pause_on_output_change);
|
|
}
|
|
|
|
static void toggle_pause_on_output_change(void *data)
|
|
{
|
|
pause_on_output_change ^= 1;
|
|
}
|
|
|
|
static void get_block_key_paste(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[block_key_paste], size);
|
|
}
|
|
|
|
static void set_block_key_paste(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &block_key_paste);
|
|
}
|
|
|
|
static void toggle_block_key_paste(void *data)
|
|
{
|
|
block_key_paste ^= 1;
|
|
}
|
|
|
|
const char * const progress_bar_names[] = {
|
|
"disabled", "line", "shuttle", "color", "color_shuttle", NULL
|
|
};
|
|
|
|
static void get_progress_bar(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, progress_bar_names[progress_bar], size);
|
|
}
|
|
|
|
static void set_progress_bar(void *data, const char *buf)
|
|
{
|
|
parse_enum(buf, 0, 4, progress_bar_names, &progress_bar);
|
|
}
|
|
|
|
static void toggle_progress_bar(void *data)
|
|
{
|
|
progress_bar++;
|
|
progress_bar %= NR_PROGRESS_BAR_MODES;
|
|
update_statusline();
|
|
}
|
|
|
|
static void get_search_resets_position(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, bool_names[search_resets_position], size);
|
|
}
|
|
|
|
static void set_search_resets_position(void *data, const char *buf)
|
|
{
|
|
parse_bool(buf, &search_resets_position);
|
|
}
|
|
|
|
static void toggle_search_resets_position(void *data)
|
|
{
|
|
search_resets_position ^= 1;
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* special callbacks (id set) {{{ */
|
|
|
|
static const char * const color_enum_names[1 + 8 * 2 + 1] = {
|
|
"default",
|
|
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "gray",
|
|
"darkgray", "lightred", "lightgreen", "lightyellow", "lightblue", "lightmagenta", "lightcyan", "white",
|
|
NULL
|
|
};
|
|
|
|
static void get_color(void *data, char *buf, size_t size)
|
|
{
|
|
int val = *(int *)data;
|
|
if (val < 16) {
|
|
strscpy(buf, color_enum_names[val + 1], size);
|
|
} else {
|
|
buf_int(buf, val, size);
|
|
}
|
|
}
|
|
|
|
static void set_color(void *data, const char *buf)
|
|
{
|
|
int color;
|
|
|
|
if (!parse_enum(buf, -1, 255, color_enum_names, &color))
|
|
return;
|
|
|
|
*(int *)data = color;
|
|
update_colors();
|
|
update_full();
|
|
}
|
|
|
|
static void get_start_view(void *data, char *buf, size_t size)
|
|
{
|
|
strscpy(buf, view_names[start_view], size);
|
|
}
|
|
|
|
static void set_start_view(void *data, const char *buf)
|
|
{
|
|
int view;
|
|
|
|
if (parse_enum(buf, 0, NR_VIEWS - 1, view_names, &view)) {
|
|
start_view = view;
|
|
}
|
|
}
|
|
|
|
static void get_attr(void *data, char *buf, size_t size)
|
|
{
|
|
int attr = *(int *)data;
|
|
|
|
if (attr == 0) {
|
|
strscpy(buf, "default", size);
|
|
return;
|
|
}
|
|
|
|
const char *standout = "";
|
|
const char *underline = "";
|
|
const char *reverse = "";
|
|
const char *blink = "";
|
|
const char *bold = "";
|
|
const char *italic = "";
|
|
|
|
if (attr & A_STANDOUT)
|
|
standout = "standout|";
|
|
if (attr & A_UNDERLINE)
|
|
underline = "underline|";
|
|
if (attr & A_REVERSE)
|
|
reverse = "reverse|";
|
|
if (attr & A_BLINK)
|
|
blink = "blink|";
|
|
if (attr & A_BOLD)
|
|
bold = "bold|";
|
|
#if HAVE_ITALIC
|
|
if (attr & A_ITALIC)
|
|
italic = "italic|";
|
|
#endif
|
|
|
|
size_t len = snprintf(buf, size, "%s%s%s%s%s%s",
|
|
standout, underline, reverse, blink, bold, italic);
|
|
|
|
if (0 < len && len < size)
|
|
buf[len - 1] = 0;
|
|
}
|
|
|
|
static void set_attr(void *data, const char *buf)
|
|
{
|
|
int attr = 0;
|
|
size_t i = 0;
|
|
size_t offset = 0;
|
|
size_t length = 0;
|
|
char* current;
|
|
|
|
do {
|
|
if (buf[i] == '|' || buf[i] == '\0') {
|
|
current = xstrndup(&buf[offset], length);
|
|
|
|
if (strcmp(current, "default") == 0)
|
|
attr |= A_NORMAL;
|
|
else if (strcmp(current, "standout") == 0)
|
|
attr |= A_STANDOUT;
|
|
else if (strcmp(current, "underline") == 0)
|
|
attr |= A_UNDERLINE;
|
|
else if (strcmp(current, "reverse") == 0)
|
|
attr |= A_REVERSE;
|
|
else if (strcmp(current, "blink") == 0)
|
|
attr |= A_BLINK;
|
|
else if (strcmp(current, "bold") == 0)
|
|
attr |= A_BOLD;
|
|
#if HAVE_ITALIC
|
|
else if (strcmp(current, "italic") == 0)
|
|
attr |= A_ITALIC;
|
|
#endif
|
|
|
|
free(current);
|
|
|
|
offset = i;
|
|
length = -1;
|
|
}
|
|
|
|
i++;
|
|
length++;
|
|
} while (buf[i - 1] != '\0');
|
|
|
|
*(int *)data = attr;
|
|
update_colors();
|
|
update_full();
|
|
}
|
|
|
|
static char **id_to_fmt(enum format_id id)
|
|
{
|
|
switch (id) {
|
|
case FMT_CLIPPED_TEXT:
|
|
return &clipped_text_format;
|
|
case FMT_CURRENT_ALT:
|
|
return ¤t_alt_format;
|
|
case FMT_PLAYLIST_ALT:
|
|
return &list_win_alt_format;
|
|
case FMT_TITLE_ALT:
|
|
return &window_title_alt_format;
|
|
case FMT_TRACKWIN_ALT:
|
|
return &track_win_alt_format;
|
|
case FMT_CURRENT:
|
|
return ¤t_format;
|
|
case FMT_HEADING_ALBUM:
|
|
return &heading_album_format;
|
|
case FMT_HEADING_ARTIST:
|
|
return &heading_artist_format;
|
|
case FMT_HEADING_PLAYLIST:
|
|
return &heading_playlist_format;
|
|
case FMT_PLAYLIST:
|
|
return &list_win_format;
|
|
case FMT_PLAYLIST_VA:
|
|
return &list_win_format_va;
|
|
case FMT_TITLE:
|
|
return &window_title_format;
|
|
case FMT_TRACKWIN:
|
|
return &track_win_format;
|
|
case FMT_TRACKWIN_ALBUM:
|
|
return &track_win_album_format;
|
|
case FMT_TRACKWIN_VA:
|
|
return &track_win_format_va;
|
|
case FMT_TREEWIN:
|
|
return &tree_win_format;
|
|
case FMT_TREEWIN_ARTIST:
|
|
return &tree_win_artist_format;
|
|
case FMT_STATUSLINE:
|
|
return &statusline_format;
|
|
default:
|
|
die("unhandled format code: %d\n", id);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void get_format(void *data, char *buf, size_t size)
|
|
{
|
|
char **fmtp = data;
|
|
|
|
strscpy(buf, *fmtp, size);
|
|
}
|
|
|
|
static void set_format(void *data, const char *buf)
|
|
{
|
|
char **fmtp = data;
|
|
|
|
if (fmtp == id_to_fmt(FMT_HEADING_ALBUM) || fmtp == id_to_fmt(FMT_HEADING_ARTIST) || fmtp == id_to_fmt(FMT_HEADING_PLAYLIST))
|
|
seen_pr1317_fmts = 1;
|
|
|
|
if (!track_format_valid(buf)) {
|
|
error_msg("invalid format string");
|
|
return;
|
|
}
|
|
free(*fmtp);
|
|
*fmtp = xstrdup(buf);
|
|
|
|
update_full();
|
|
}
|
|
|
|
static void set_clipped_text_format(void *data, const char *buf)
|
|
{
|
|
free(clipped_text_format);
|
|
clipped_text_format = clipped_text_internal = xstrdup(buf);
|
|
|
|
update_full();
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
#define DN(name) { #name, get_ ## name, set_ ## name, NULL, 0 },
|
|
#define DN_FLAGS(name, flags) { #name, get_ ## name, set_ ## name, NULL, flags },
|
|
#define DT(name) { #name, get_ ## name, set_ ## name, toggle_ ## name, 0 },
|
|
|
|
static const struct {
|
|
const char *name;
|
|
opt_get_cb get;
|
|
opt_set_cb set;
|
|
opt_toggle_cb toggle;
|
|
unsigned int flags;
|
|
} simple_options[] = {
|
|
DT(aaa_mode)
|
|
DT(auto_reshuffle)
|
|
DN_FLAGS(device, OPT_PROGRAM_PATH)
|
|
DN(buffer_seconds)
|
|
DN(scroll_offset)
|
|
DN(rewind_offset)
|
|
DT(confirm_run)
|
|
DT(continue)
|
|
DT(continue_album)
|
|
DT(smart_artist_sort)
|
|
DT(sort_albums_by_name)
|
|
DN(id3_default_charset)
|
|
DN(icecast_default_charset)
|
|
DN(lib_sort)
|
|
DN(output_plugin)
|
|
DN(passwd)
|
|
DN(pl_sort)
|
|
DT(play_library)
|
|
DT(play_sorted)
|
|
DT(display_artist_sort_name)
|
|
DT(repeat)
|
|
DT(repeat_current)
|
|
DT(replaygain)
|
|
DT(replaygain_limit)
|
|
DN(replaygain_preamp)
|
|
DT(resume)
|
|
DT(show_hidden)
|
|
DT(auto_expand_albums_follow)
|
|
DT(auto_expand_albums_search)
|
|
DT(auto_expand_albums_selcur)
|
|
DT(auto_hide_playlists_panel)
|
|
DT(show_all_tracks)
|
|
DT(show_current_bitrate)
|
|
DT(show_playback_position)
|
|
DT(show_remaining_time)
|
|
DT(set_term_title)
|
|
DT(shuffle)
|
|
DT(follow)
|
|
DT(softvol)
|
|
DN(softvol_state)
|
|
DN_FLAGS(status_display_program, OPT_PROGRAM_PATH)
|
|
DT(wrap_search)
|
|
DT(skip_track_info)
|
|
DT(ignore_duplicates)
|
|
DT(mouse)
|
|
DT(mpris)
|
|
DT(time_show_leading_zero)
|
|
DN(lib_add_filter)
|
|
DN(start_view)
|
|
DT(stop_after_queue)
|
|
DN(tree_width_percent)
|
|
DN(tree_width_max)
|
|
DT(pause_on_output_change)
|
|
DN(pl_env_vars)
|
|
DT(block_key_paste)
|
|
DT(progress_bar)
|
|
DT(search_resets_position)
|
|
{ NULL, NULL, NULL, NULL, 0 }
|
|
};
|
|
|
|
static const char * const color_names[NR_COLORS] = {
|
|
"color_cmdline_bg",
|
|
"color_cmdline_fg",
|
|
"color_error",
|
|
"color_info",
|
|
"color_separator",
|
|
"color_statusline_bg",
|
|
"color_statusline_fg",
|
|
"color_statusline_progress_bg",
|
|
"color_statusline_progress_fg",
|
|
"color_titleline_bg",
|
|
"color_titleline_fg",
|
|
"color_win_bg",
|
|
"color_win_cur",
|
|
"color_win_cur_sel_bg",
|
|
"color_win_cur_sel_fg",
|
|
"color_win_dir",
|
|
"color_win_fg",
|
|
"color_win_inactive_cur_sel_bg",
|
|
"color_win_inactive_cur_sel_fg",
|
|
"color_win_inactive_sel_bg",
|
|
"color_win_inactive_sel_fg",
|
|
"color_win_sel_bg",
|
|
"color_win_sel_fg",
|
|
"color_win_title_bg",
|
|
"color_win_title_fg",
|
|
"color_trackwin_album_bg",
|
|
"color_trackwin_album_fg",
|
|
};
|
|
|
|
static const char * const attr_names[NR_ATTRS] = {
|
|
"color_cmdline_attr",
|
|
"color_statusline_attr",
|
|
"color_statusline_progress_attr",
|
|
"color_titleline_attr",
|
|
"color_win_attr",
|
|
"color_win_cur_sel_attr",
|
|
"color_cur_sel_attr",
|
|
"color_win_inactive_cur_sel_attr",
|
|
"color_win_inactive_sel_attr",
|
|
"color_win_sel_attr",
|
|
"color_win_title_attr",
|
|
"color_trackwin_album_attr",
|
|
"color_win_cur_attr",
|
|
};
|
|
|
|
LIST_HEAD(option_head);
|
|
int nr_options = 0;
|
|
|
|
void option_add(const char *name, const void *data, opt_get_cb get,
|
|
opt_set_cb set, opt_toggle_cb toggle, unsigned int flags)
|
|
{
|
|
struct cmus_opt *opt = xnew(struct cmus_opt, 1);
|
|
struct list_head *item;
|
|
|
|
opt->name = name;
|
|
opt->data = (void *)data;
|
|
opt->get = get;
|
|
opt->set = set;
|
|
opt->toggle = toggle;
|
|
opt->flags = flags;
|
|
|
|
item = option_head.next;
|
|
while (item != &option_head) {
|
|
struct cmus_opt *o = container_of(item, struct cmus_opt, node);
|
|
|
|
if (strcmp(name, o->name) < 0)
|
|
break;
|
|
item = item->next;
|
|
}
|
|
/* add before item */
|
|
list_add_tail(&opt->node, item);
|
|
nr_options++;
|
|
}
|
|
|
|
struct cmus_opt *option_find(const char *name)
|
|
{
|
|
struct cmus_opt *opt = option_find_silent(name);
|
|
if (opt == NULL)
|
|
error_msg("no such option %s", name);
|
|
return opt;
|
|
}
|
|
|
|
struct cmus_opt *option_find_silent(const char *name)
|
|
{
|
|
struct cmus_opt *opt;
|
|
|
|
list_for_each_entry(opt, &option_head, node) {
|
|
if (strcmp(name, opt->name) == 0)
|
|
return opt;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void option_set(const char *name, const char *value)
|
|
{
|
|
struct cmus_opt *opt = option_find(name);
|
|
|
|
if (opt)
|
|
opt->set(opt->data, value);
|
|
}
|
|
|
|
void options_add(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; simple_options[i].name; i++)
|
|
option_add(simple_options[i].name, NULL, simple_options[i].get,
|
|
simple_options[i].set, simple_options[i].toggle,
|
|
simple_options[i].flags);
|
|
|
|
for (i = 0; i < NR_FMTS; i++)
|
|
option_add(str_defaults[i].name, id_to_fmt(i), get_format,
|
|
set_format, NULL, 0);
|
|
|
|
option_find("format_clipped_text")->set = set_clipped_text_format;
|
|
|
|
for (i = 0; i < NR_COLORS; i++)
|
|
option_add(color_names[i], &colors[i], get_color, set_color,
|
|
NULL, 0);
|
|
|
|
for (i = 0; i < NR_ATTRS; i++)
|
|
option_add(attr_names[i], &attrs[i], get_attr, set_attr, NULL,
|
|
0);
|
|
|
|
ip_add_options();
|
|
op_add_options();
|
|
}
|
|
|
|
static int handle_line(void *data, const char *line)
|
|
{
|
|
run_command(line);
|
|
return 0;
|
|
}
|
|
|
|
int source_file(const char *filename)
|
|
{
|
|
return file_for_each_line(filename, handle_line, NULL);
|
|
}
|
|
|
|
void options_load(void)
|
|
{
|
|
char filename[512];
|
|
int i;
|
|
|
|
/* initialize those that can't be statically initialized */
|
|
cdda_device = get_default_cdda_device();
|
|
for (i = 0; str_defaults[i].name; i++)
|
|
option_set(str_defaults[i].name, str_defaults[i].value);
|
|
|
|
/* reset the flag before loading the autosave */
|
|
seen_pr1317_fmts = 0;
|
|
|
|
/* load autosave config */
|
|
snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
|
|
if (source_file(filename) == -1) {
|
|
char *def = xstrjoin(cmus_data_dir, "/rc");
|
|
|
|
if (errno != ENOENT)
|
|
error_msg("loading %s: %s", filename, strerror(errno));
|
|
|
|
/* load defaults */
|
|
if (source_file(def) == -1)
|
|
die_errno("loading %s", def);
|
|
|
|
free(def);
|
|
}
|
|
|
|
/* replace old format with new one if upgrading past PR #1317 */
|
|
if (!seen_pr1317_fmts) {
|
|
option_set(str_defaults[FMT_STATUSLINE].name, str_defaults[FMT_STATUSLINE].value);
|
|
option_set(str_defaults[FMT_PLAYLIST_ALT].name, str_defaults[FMT_PLAYLIST_ALT].value);
|
|
option_set(str_defaults[FMT_TRACKWIN_ALBUM].name, str_defaults[FMT_TRACKWIN_ALBUM].value);
|
|
option_set(str_defaults[FMT_TRACKWIN].name, str_defaults[FMT_TRACKWIN].value);
|
|
option_set(str_defaults[FMT_TRACKWIN_VA].name, str_defaults[FMT_TRACKWIN_VA].value);
|
|
}
|
|
|
|
/* load optional static config */
|
|
snprintf(filename, sizeof(filename), "%s/rc", cmus_config_dir);
|
|
if (source_file(filename) == -1) {
|
|
if (errno != ENOENT)
|
|
error_msg("loading %s: %s", filename, strerror(errno));
|
|
}
|
|
|
|
/* replace the default format_clipped_text symbol in ascii terminal */
|
|
if (!using_utf8 && strcmp(clipped_text_format, str_defaults[FMT_CLIPPED_TEXT].value) == 0) {
|
|
clipped_text_internal = xstrdup("...");
|
|
}
|
|
}
|
|
|
|
void options_exit(void)
|
|
{
|
|
struct cmus_opt *opt;
|
|
struct filter_entry *filt;
|
|
char filename_tmp[512];
|
|
char filename[512];
|
|
FILE *f;
|
|
int i;
|
|
|
|
snprintf(filename_tmp, sizeof(filename_tmp), "%s/autosave.tmp", cmus_config_dir);
|
|
f = fopen(filename_tmp, "w");
|
|
if (f == NULL) {
|
|
warn_errno("creating %s", filename_tmp);
|
|
return;
|
|
}
|
|
|
|
/* save options */
|
|
list_for_each_entry(opt, &option_head, node) {
|
|
char buf[OPTION_MAX_SIZE];
|
|
|
|
buf[0] = 0;
|
|
opt->get(opt->data, buf, OPTION_MAX_SIZE);
|
|
fprintf(f, "set %s=%s\n", opt->name, buf);
|
|
}
|
|
|
|
/* save key bindings */
|
|
for (i = 0; i < NR_CTXS; i++) {
|
|
struct binding *b = key_bindings[i];
|
|
|
|
while (b) {
|
|
fprintf(f, "bind %s %s %s\n", key_context_names[i], b->key->name, b->cmd);
|
|
b = b->next;
|
|
}
|
|
}
|
|
|
|
/* save filters */
|
|
list_for_each_entry(filt, &filters_head, node)
|
|
fprintf(f, "fset %s=%s\n", filt->name, filt->filter);
|
|
fprintf(f, "factivate");
|
|
list_for_each_entry(filt, &filters_head, node) {
|
|
switch (filt->act_stat) {
|
|
case FS_YES:
|
|
fprintf(f, " %s", filt->name);
|
|
break;
|
|
case FS_NO:
|
|
fprintf(f, " !%s", filt->name);
|
|
break;
|
|
}
|
|
}
|
|
fprintf(f, "\n");
|
|
|
|
fclose(f);
|
|
|
|
snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
|
|
i = rename(filename_tmp, filename);
|
|
if (i)
|
|
warn_errno("renaming %s to %s", filename_tmp, filename);
|
|
}
|
|
|
|
struct resume {
|
|
enum player_status status;
|
|
char *filename;
|
|
long int position;
|
|
char *lib_filename;
|
|
int view;
|
|
char *live_filter;
|
|
char *browser_dir;
|
|
char *marked_pl;
|
|
};
|
|
|
|
static int handle_resume_line(void *data, const char *line)
|
|
{
|
|
struct resume *resume = data;
|
|
char *cmd, *arg;
|
|
|
|
if (!parse_command(line, &cmd, &arg))
|
|
return 0;
|
|
if (!arg)
|
|
goto out;
|
|
|
|
if (strcmp(cmd, "status") == 0) {
|
|
parse_enum(arg, 0, NR_PLAYER_STATUS, player_status_names, (int *) &resume->status);
|
|
} else if (strcmp(cmd, "file") == 0) {
|
|
free(resume->filename);
|
|
resume->filename = xstrdup(unescape(arg));
|
|
} else if (strcmp(cmd, "position") == 0) {
|
|
str_to_int(arg, &resume->position);
|
|
} else if (strcmp(cmd, "lib_file") == 0) {
|
|
free(resume->lib_filename);
|
|
resume->lib_filename = xstrdup(unescape(arg));
|
|
} else if (strcmp(cmd, "view") == 0) {
|
|
parse_enum(arg, 0, NR_VIEWS, view_names, &resume->view);
|
|
} else if (strcmp(cmd, "live-filter") == 0) {
|
|
free(resume->live_filter);
|
|
resume->live_filter = xstrdup(unescape(arg));
|
|
} else if (strcmp(cmd, "browser-dir") == 0) {
|
|
free(resume->browser_dir);
|
|
resume->browser_dir = xstrdup(unescape(arg));
|
|
} else if (strcmp(cmd, "active-pl") == 0) {
|
|
free(pl_resume_name);
|
|
pl_resume_name = xstrdup(unescape(arg));
|
|
} else if (strcmp(cmd, "active-pl-row") == 0) {
|
|
str_to_int(arg, &pl_resume_row);
|
|
} else if (strcmp(cmd, "marked-pl") == 0) {
|
|
free(resume->marked_pl);
|
|
resume->marked_pl = xstrdup(unescape(arg));
|
|
}
|
|
|
|
free(arg);
|
|
out:
|
|
free(cmd);
|
|
return 0;
|
|
}
|
|
|
|
void resume_load(void)
|
|
{
|
|
char filename[512];
|
|
struct track_info *ti, *old;
|
|
struct resume resume = { .status = PLAYER_STATUS_STOPPED, .view = -1 };
|
|
|
|
snprintf(filename, sizeof(filename), "%s/resume", cmus_config_dir);
|
|
if (file_for_each_line(filename, handle_resume_line, &resume) == -1) {
|
|
if (errno != ENOENT)
|
|
error_msg("loading %s: %s", filename, strerror(errno));
|
|
return;
|
|
}
|
|
if (resume.view >= 0 && resume.view != cur_view)
|
|
set_view(resume.view);
|
|
if (resume.lib_filename) {
|
|
cache_lock();
|
|
ti = old = cache_get_ti(resume.lib_filename, 0);
|
|
cache_unlock();
|
|
if (ti) {
|
|
lib_add_track(ti, NULL);
|
|
track_info_unref(ti);
|
|
lib_store_cur_track(ti);
|
|
track_info_unref(ti);
|
|
ti = lib_set_track(lib_find_track(ti));
|
|
if (ti) {
|
|
BUG_ON(ti != old);
|
|
track_info_unref(ti);
|
|
tree_sel_current(auto_expand_albums_follow);
|
|
sorted_sel_current();
|
|
}
|
|
}
|
|
free(resume.lib_filename);
|
|
}
|
|
if (resume.filename) {
|
|
cache_lock();
|
|
ti = cache_get_ti(resume.filename, 0);
|
|
cache_unlock();
|
|
if (ti) {
|
|
player_set_file(ti);
|
|
if (resume.status != PLAYER_STATUS_STOPPED)
|
|
player_seek(resume.position, 0, resume.status == PLAYER_STATUS_PLAYING);
|
|
}
|
|
free(resume.filename);
|
|
}
|
|
if (resume.live_filter) {
|
|
filters_set_live(resume.live_filter);
|
|
free(resume.live_filter);
|
|
}
|
|
if (resume.browser_dir) {
|
|
browser_chdir(resume.browser_dir);
|
|
free(resume.browser_dir);
|
|
}
|
|
if (resume.marked_pl) {
|
|
pl_set_marked_pl_by_name(resume.marked_pl);
|
|
free(resume.marked_pl);
|
|
}
|
|
}
|
|
|
|
void resume_exit(void)
|
|
{
|
|
char filename_tmp[512];
|
|
char filename[512];
|
|
const char *pl_name;
|
|
struct track_info *ti;
|
|
FILE *f;
|
|
int rc;
|
|
|
|
snprintf(filename_tmp, sizeof(filename_tmp), "%s/resume.tmp", cmus_config_dir);
|
|
f = fopen(filename_tmp, "w");
|
|
if (!f) {
|
|
warn_errno("creating %s", filename_tmp);
|
|
return;
|
|
}
|
|
|
|
fprintf(f, "status %s\n", player_status_names[player_info.status]);
|
|
ti = player_info.ti;
|
|
if (ti) {
|
|
fprintf(f, "file %s\n", escape(ti->filename));
|
|
fprintf(f, "position %d\n", player_info.pos);
|
|
}
|
|
if (lib_cur_track)
|
|
ti = tree_track_info(lib_cur_track);
|
|
else
|
|
ti = lib_get_cur_stored_track();
|
|
if (ti)
|
|
fprintf(f, "lib_file %s\n", escape(ti->filename));
|
|
fprintf(f, "view %s\n", view_names[cur_view]);
|
|
if (lib_live_filter)
|
|
fprintf(f, "live-filter %s\n", escape(lib_live_filter));
|
|
fprintf(f, "browser-dir %s\n", escape(browser_dir));
|
|
|
|
if ((pl_name = pl_playing_pl_name())) {
|
|
fprintf(f, "active-pl %s\n", escape(pl_name));
|
|
fprintf(f, "active-pl-row %d\n", pl_playing_pl_row());
|
|
}
|
|
|
|
fprintf(f, "marked-pl %s\n", escape(pl_marked_pl_name()));
|
|
|
|
fclose(f);
|
|
|
|
snprintf(filename, sizeof(filename), "%s/resume", cmus_config_dir);
|
|
rc = rename(filename_tmp, filename);
|
|
if (rc)
|
|
warn_errno("renaming %s to %s", filename_tmp, filename);
|
|
}
|