cmus-remote: Read command results
[cmus.git] / options.c
blob7ea31aaf92df72b41bc75268992b1d9f31cd1b13
1 /*
2 * Copyright 2006 Timo Hirvonen
3 */
5 #include "options.h"
6 #include "list.h"
7 #include "utils.h"
8 #include "xmalloc.h"
9 #include "player.h"
10 #include "buffer.h"
11 #include "ui_curses.h"
12 #include "format_print.h"
13 #include "cmus.h"
14 #include "misc.h"
15 #include "lib.h"
16 #include "pl.h"
17 #include "browser.h"
18 #include "keys.h"
19 #include "filters.h"
20 #include "command_mode.h"
21 #include "file.h"
22 #include "prog.h"
23 #include "output.h"
24 #include "config/datadir.h"
26 #include <stdio.h>
27 #include <errno.h>
29 #if defined(__sun__)
30 #include <ncurses.h>
31 #else
32 #include <curses.h>
33 #endif
35 /* initialized option variables */
37 char *output_plugin = NULL;
38 char *status_display_program = NULL;
39 char *server_password;
40 int auto_reshuffle = 0;
41 int confirm_run = 1;
42 int show_hidden = 0;
43 int show_remaining_time = 0;
44 int set_term_title = 1;
45 int play_library = 1;
46 int repeat = 0;
47 int shuffle = 0;
49 int colors[NR_COLORS] = {
50 -1,
51 -1,
52 COLOR_RED | BRIGHT,
53 COLOR_YELLOW | BRIGHT,
55 COLOR_BLUE,
56 COLOR_WHITE,
57 COLOR_BLACK,
58 COLOR_BLUE,
60 COLOR_WHITE | BRIGHT,
61 -1,
62 COLOR_YELLOW | BRIGHT,
63 COLOR_BLUE,
65 COLOR_YELLOW | BRIGHT,
66 COLOR_BLUE | BRIGHT,
67 -1,
68 COLOR_WHITE,
70 COLOR_YELLOW | BRIGHT,
71 COLOR_WHITE,
72 COLOR_BLACK,
73 COLOR_BLUE,
75 COLOR_WHITE | BRIGHT,
76 COLOR_BLUE,
77 COLOR_WHITE | BRIGHT
80 /* uninitialized option variables */
81 char *track_win_format = NULL;
82 char *track_win_alt_format = NULL;
83 char *list_win_format = NULL;
84 char *list_win_alt_format = NULL;
85 char *current_format = NULL;
86 char *current_alt_format = NULL;
87 char *window_title_format = NULL;
88 char *window_title_alt_format = NULL;
89 char *id3_default_charset = NULL;
91 static void buf_int(char *buf, int val)
93 snprintf(buf, OPTION_MAX_SIZE, "%d", val);
96 static int parse_int(const char *buf, int minval, int maxval, int *val)
98 long int tmp;
100 if (str_to_int(buf, &tmp) == -1 || tmp < minval || tmp > maxval) {
101 error_msg("integer in range %d..%d expected", minval, maxval);
102 return 0;
104 *val = tmp;
105 return 1;
108 int parse_enum(const char *buf, int minval, int maxval, const char * const names[], int *val)
110 long int tmp;
111 int i;
113 if (str_to_int(buf, &tmp) == 0) {
114 if (tmp < minval || tmp > maxval)
115 goto err;
116 *val = tmp;
117 return 1;
120 for (i = 0; names[i]; i++) {
121 if (strcasecmp(buf, names[i]) == 0) {
122 *val = i + minval;
123 return 1;
126 err:
127 error_msg("name or integer in range %d..%d expected", minval, maxval);
128 return 0;
131 static const char * const bool_names[] = {
132 "false", "true", NULL
135 static int parse_bool(const char *buf, int *val)
137 return parse_enum(buf, 0, 1, bool_names, val);
140 /* this is used as id in struct cmus_opt */
141 enum format_id {
142 FMT_CURRENT_ALT,
143 FMT_PLAYLIST_ALT,
144 FMT_TITLE_ALT,
145 FMT_TRACKWIN_ALT,
146 FMT_CURRENT,
147 FMT_PLAYLIST,
148 FMT_TITLE,
149 FMT_TRACKWIN
151 #define NR_FMTS 8
153 /* callbacks for normal options {{{ */
155 #define SECOND_SIZE (44100 * 16 / 8 * 2)
156 static void get_buffer_seconds(unsigned int id, char *buf)
158 buf_int(buf, (player_get_buffer_chunks() * CHUNK_SIZE + SECOND_SIZE / 2) / SECOND_SIZE);
161 static void set_buffer_seconds(unsigned int id, const char *buf)
163 int sec;
165 if (parse_int(buf, 1, 20, &sec))
166 player_set_buffer_chunks((sec * SECOND_SIZE + CHUNK_SIZE / 2) / CHUNK_SIZE);
169 static void get_id3_default_charset(unsigned int id, char *buf)
171 strcpy(buf, id3_default_charset);
174 static void set_id3_default_charset(unsigned int id, const char *buf)
176 free(id3_default_charset);
177 id3_default_charset = xstrdup(buf);
180 static const char * const valid_sort_keys[] = {
181 "artist",
182 "album",
183 "title",
184 "tracknumber",
185 "discnumber",
186 "date",
187 "genre",
188 "filename",
189 "albumartist",
190 NULL
193 static const char **parse_sort_keys(const char *value)
195 const char **keys;
196 const char *s, *e;
197 int size = 4;
198 int pos = 0;
200 size = 4;
201 keys = xnew(const char *, size);
203 s = value;
204 while (1) {
205 char buf[32];
206 int i, len;
208 while (*s == ' ')
209 s++;
211 e = s;
212 while (*e && *e != ' ')
213 e++;
215 len = e - s;
216 if (len == 0)
217 break;
218 if (len > 31)
219 len = 31;
221 memcpy(buf, s, len);
222 buf[len] = 0;
223 s = e;
225 for (i = 0; ; i++) {
226 if (valid_sort_keys[i] == NULL) {
227 error_msg("invalid sort key '%s'", buf);
228 free(keys);
229 return NULL;
232 if (strcmp(buf, valid_sort_keys[i]) == 0)
233 break;
236 if (pos == size - 1) {
237 size *= 2;
238 keys = xrenew(const char *, keys, size);
240 keys[pos++] = valid_sort_keys[i];
242 keys[pos] = NULL;
243 return keys;
246 static void get_lib_sort(unsigned int id, char *buf)
248 strcpy(buf, lib_editable.sort_str);
251 static void set_lib_sort(unsigned int id, const char *buf)
253 const char **keys = parse_sort_keys(buf);
255 if (keys)
256 editable_set_sort_keys(&lib_editable, keys);
259 static void get_pl_sort(unsigned int id, char *buf)
261 strcpy(buf, pl_editable.sort_str);
264 static void set_pl_sort(unsigned int id, const char *buf)
266 const char **keys = parse_sort_keys(buf);
268 if (keys)
269 editable_set_sort_keys(&pl_editable, keys);
272 static void get_output_plugin(unsigned int id, char *buf)
274 char *value = player_get_op();
276 if (value)
277 strcpy(buf, value);
278 free(value);
281 static void set_output_plugin(unsigned int id, const char *buf)
283 if (ui_initialized) {
284 if (!soft_vol)
285 mixer_close();
286 player_set_op(buf);
287 if (!soft_vol)
288 mixer_open();
289 } else {
290 /* must set it later manually */
291 output_plugin = xstrdup(buf);
295 static void get_passwd(unsigned int id, char *buf)
297 if (server_password)
298 strcpy(buf, server_password);
301 static void set_passwd(unsigned int id, const char *buf)
303 int len = strlen(buf);
305 if (len == 0) {
306 free(server_password);
307 server_password = NULL;
308 } else if (len < 6) {
309 error_msg("unsafe password");
310 } else {
311 free(server_password);
312 server_password = xstrdup(buf);
316 static void get_replaygain_preamp(unsigned int id, char *buf)
318 sprintf(buf, "%f", replaygain_preamp);
321 static void set_replaygain_preamp(unsigned int id, const char *buf)
323 double val;
324 char *end;
326 val = strtod(buf, &end);
327 if (end == buf) {
328 error_msg("floating point number expected (dB)");
329 return;
331 player_set_rg_preamp(val);
334 static void get_softvol_state(unsigned int id, char *buf)
336 sprintf(buf, "%d %d", soft_vol_l, soft_vol_r);
339 static void set_softvol_state(unsigned int id, const char *buf)
341 char buffer[OPTION_MAX_SIZE];
342 char *ptr;
343 long int l, r;
345 strcpy(buffer, buf);
346 ptr = strchr(buffer, ' ');
347 if (!ptr)
348 goto err;
349 while (*ptr == ' ')
350 *ptr++ = 0;
352 if (str_to_int(buffer, &l) == -1 || l < 0 || l > 100)
353 goto err;
354 if (str_to_int(ptr, &r) == -1 || r < 0 || r > 100)
355 goto err;
357 player_set_soft_volume(l, r);
358 return;
359 err:
360 error_msg("two integers in range 0..100 expected");
363 static void get_status_display_program(unsigned int id, char *buf)
365 if (status_display_program)
366 strcpy(buf, status_display_program);
369 static void set_status_display_program(unsigned int id, const char *buf)
371 free(status_display_program);
372 status_display_program = NULL;
373 if (buf[0])
374 status_display_program = xstrdup(buf);
377 /* }}} */
379 /* callbacks for toggle options {{{ */
381 static void get_auto_reshuffle(unsigned int id, char *buf)
383 strcpy(buf, bool_names[auto_reshuffle]);
386 static void set_auto_reshuffle(unsigned int id, const char *buf)
388 parse_bool(buf, &auto_reshuffle);
391 static void toggle_auto_reshuffle(unsigned int id)
393 auto_reshuffle ^= 1;
396 static void get_continue(unsigned int id, char *buf)
398 strcpy(buf, bool_names[player_cont]);
401 static void set_continue(unsigned int id, const char *buf)
403 if (!parse_bool(buf, &player_cont))
404 return;
405 update_statusline();
408 static void toggle_continue(unsigned int id)
410 player_cont ^= 1;
411 update_statusline();
414 static void get_confirm_run(unsigned int id, char *buf)
416 strcpy(buf, bool_names[confirm_run]);
419 static void set_confirm_run(unsigned int id, const char *buf)
421 parse_bool(buf, &confirm_run);
424 static void toggle_confirm_run(unsigned int id)
426 confirm_run ^= 1;
429 const char * const view_names[NR_VIEWS + 1] = {
430 "tree", "sorted", "playlist", "queue", "browser", "filters", "settings", NULL
433 static void get_play_library(unsigned int id, char *buf)
435 strcpy(buf, bool_names[play_library]);
438 static void set_play_library(unsigned int id, const char *buf)
440 if (!parse_bool(buf, &play_library))
441 return;
442 update_statusline();
445 static void toggle_play_library(unsigned int id)
447 play_library ^= 1;
448 update_statusline();
451 static void get_play_sorted(unsigned int id, char *buf)
453 strcpy(buf, bool_names[play_sorted]);
456 static void set_play_sorted(unsigned int id, const char *buf)
458 int tmp;
460 if (!parse_bool(buf, &tmp))
461 return;
463 play_sorted = tmp;
464 update_statusline();
467 static void toggle_play_sorted(unsigned int id)
469 editable_lock();
470 play_sorted = play_sorted ^ 1;
472 /* shuffle would override play_sorted... */
473 if (play_sorted) {
474 /* play_sorted makes no sense in playlist */
475 play_library = 1;
476 shuffle = 0;
479 editable_unlock();
480 update_statusline();
483 const char * const aaa_mode_names[] = {
484 "all", "artist", "album", NULL
487 static void get_aaa_mode(unsigned int id, char *buf)
489 strcpy(buf, aaa_mode_names[aaa_mode]);
492 static void set_aaa_mode(unsigned int id, const char *buf)
494 int tmp;
496 if (!parse_enum(buf, 0, 2, aaa_mode_names, &tmp))
497 return;
499 aaa_mode = tmp;
500 update_statusline();
503 static void toggle_aaa_mode(unsigned int id)
505 editable_lock();
507 /* aaa mode makes no sense in playlist */
508 play_library = 1;
510 aaa_mode++;
511 aaa_mode %= 3;
512 editable_unlock();
513 update_statusline();
516 static void get_repeat(unsigned int id, char *buf)
518 strcpy(buf, bool_names[repeat]);
521 static void set_repeat(unsigned int id, const char *buf)
523 if (!parse_bool(buf, &repeat))
524 return;
525 update_statusline();
528 static void toggle_repeat(unsigned int id)
530 repeat ^= 1;
531 update_statusline();
534 static const char * const replaygain_names[] = {
535 "disabled", "track", "album", NULL
538 static void get_replaygain(unsigned int id, char *buf)
540 strcpy(buf, replaygain_names[replaygain]);
543 static void set_replaygain(unsigned int id, const char *buf)
545 int tmp;
547 if (!parse_enum(buf, 0, 2, replaygain_names, &tmp))
548 return;
549 player_set_rg(tmp);
552 static void toggle_replaygain(unsigned int id)
554 player_set_rg((replaygain + 1) % 3);
557 static void get_replaygain_limit(unsigned int id, char *buf)
559 strcpy(buf, bool_names[replaygain_limit]);
562 static void set_replaygain_limit(unsigned int id, const char *buf)
564 int tmp;
566 if (!parse_bool(buf, &tmp))
567 return;
568 player_set_rg_limit(tmp);
571 static void toggle_replaygain_limit(unsigned int id)
573 player_set_rg_limit(replaygain_limit ^ 1);
576 static void get_show_hidden(unsigned int id, char *buf)
578 strcpy(buf, bool_names[show_hidden]);
581 static void set_show_hidden(unsigned int id, const char *buf)
583 if (!parse_bool(buf, &show_hidden))
584 return;
585 browser_reload();
588 static void toggle_show_hidden(unsigned int id)
590 show_hidden ^= 1;
591 browser_reload();
594 static void get_show_remaining_time(unsigned int id, char *buf)
596 strcpy(buf, bool_names[show_remaining_time]);
599 static void set_show_remaining_time(unsigned int id, const char *buf)
601 if (!parse_bool(buf, &show_remaining_time))
602 return;
603 update_statusline();
606 static void toggle_show_remaining_time(unsigned int id)
608 show_remaining_time ^= 1;
609 update_statusline();
612 static void get_set_term_title(unsigned int id, char *buf)
614 strcpy(buf, bool_names[set_term_title]);
617 static void set_set_term_title(unsigned int id, const char *buf)
619 parse_bool(buf, &set_term_title);
622 static void toggle_set_term_title(unsigned int id)
624 set_term_title ^= 1;
627 static void get_shuffle(unsigned int id, char *buf)
629 strcpy(buf, bool_names[shuffle]);
632 static void set_shuffle(unsigned int id, const char *buf)
634 if (!parse_bool(buf, &shuffle))
635 return;
636 update_statusline();
639 static void toggle_shuffle(unsigned int id)
641 shuffle ^= 1;
642 update_statusline();
645 static void get_softvol(unsigned int id, char *buf)
647 strcpy(buf, bool_names[soft_vol]);
650 static void do_set_softvol(int soft)
652 if (!soft_vol)
653 mixer_close();
654 player_set_soft_vol(soft);
655 if (!soft_vol)
656 mixer_open();
657 update_statusline();
660 static void set_softvol(unsigned int id, const char *buf)
662 int soft;
664 if (!parse_bool(buf, &soft))
665 return;
666 do_set_softvol(soft);
669 static void toggle_softvol(unsigned int id)
671 do_set_softvol(soft_vol ^ 1);
674 /* }}} */
676 /* special callbacks (id set) {{{ */
678 static const char * const color_enum_names[1 + 8 * 2 + 1] = {
679 "default",
680 "black", "red", "green", "yellow", "blue", "magenta", "cyan", "gray",
681 "darkgray", "lightred", "lightgreen", "lightyellow", "lightblue", "lightmagenta", "lightcyan", "white",
682 NULL
685 static void get_color(unsigned int id, char *buf)
687 int val;
689 val = colors[id];
690 if (val < 16) {
691 strcpy(buf, color_enum_names[val + 1]);
692 } else {
693 buf_int(buf, val);
697 static void set_color(unsigned int id, const char *buf)
699 int color;
701 if (!parse_enum(buf, -1, 255, color_enum_names, &color))
702 return;
704 colors[id] = color;
705 update_colors();
706 update_full();
709 static char **id_to_fmt(enum format_id id)
711 switch (id) {
712 case FMT_CURRENT_ALT:
713 return &current_alt_format;
714 case FMT_PLAYLIST_ALT:
715 return &list_win_alt_format;
716 case FMT_TITLE_ALT:
717 return &window_title_alt_format;
718 case FMT_TRACKWIN_ALT:
719 return &track_win_alt_format;
720 case FMT_CURRENT:
721 return &current_format;
722 case FMT_PLAYLIST:
723 return &list_win_format;
724 case FMT_TITLE:
725 return &window_title_format;
726 case FMT_TRACKWIN:
727 return &track_win_format;
729 return NULL;
732 static void get_format(unsigned int id, char *buf)
734 char **fmtp = id_to_fmt(id);
736 strcpy(buf, *fmtp);
739 static void set_format(unsigned int id, const char *buf)
741 char **fmtp = id_to_fmt(id);
743 if (!format_valid(buf)) {
744 error_msg("invalid format string");
745 return;
747 free(*fmtp);
748 *fmtp = xstrdup(buf);
750 update_full();
753 /* }}} */
755 #define DN(name) { #name, get_ ## name, set_ ## name, NULL },
756 #define DT(name) { #name, get_ ## name, set_ ## name, toggle_ ## name },
758 static const struct {
759 const char *name;
760 opt_get_cb get;
761 opt_set_cb set;
762 opt_toggle_cb toggle;
763 } simple_options[] = {
764 DT(aaa_mode)
765 DT(auto_reshuffle)
766 DN(buffer_seconds)
767 DT(confirm_run)
768 DT(continue)
769 DN(id3_default_charset)
770 DN(lib_sort)
771 DN(output_plugin)
772 DN(passwd)
773 DN(pl_sort)
774 DT(play_library)
775 DT(play_sorted)
776 DT(repeat)
777 DT(replaygain)
778 DT(replaygain_limit)
779 DN(replaygain_preamp)
780 DT(show_hidden)
781 DT(show_remaining_time)
782 DT(set_term_title)
783 DT(shuffle)
784 DT(softvol)
785 DN(softvol_state)
786 DN(status_display_program)
787 { NULL, NULL, NULL, NULL }
790 static const char * const color_names[NR_COLORS] = {
791 "color_cmdline_bg",
792 "color_cmdline_fg",
793 "color_error",
794 "color_info",
795 "color_separator",
796 "color_statusline_bg",
797 "color_statusline_fg",
798 "color_titleline_bg",
799 "color_titleline_fg",
800 "color_win_bg",
801 "color_win_cur",
802 "color_win_cur_sel_bg",
803 "color_win_cur_sel_fg",
804 "color_win_dir",
805 "color_win_fg",
806 "color_win_inactive_cur_sel_bg",
807 "color_win_inactive_cur_sel_fg",
808 "color_win_inactive_sel_bg",
809 "color_win_inactive_sel_fg",
810 "color_win_sel_bg",
811 "color_win_sel_fg",
812 "color_win_title_bg",
813 "color_win_title_fg"
816 /* default values for the variables which we must initialize but
817 * can't do it statically */
818 static const struct {
819 const char *name;
820 const char *value;
821 } str_defaults[] = {
822 { "altformat_current", " %F " },
823 { "altformat_playlist", " %f%= %d " },
824 { "altformat_title", "%f" },
825 { "altformat_trackwin", " %f%= %d " },
826 { "format_current", " %a - %l - %02n. %t%= %y " },
827 { "format_playlist", " %-20a %02n. %t%= %y %d " },
828 { "format_title", "%a - %l - %t (%y)" },
829 { "format_trackwin", " %02n. %t%= %y %d " },
831 { "lib_sort" , "artist album discnumber tracknumber title filename" },
832 { "pl_sort", "" },
833 { "id3_default_charset","ISO-8859-1" },
834 { NULL, NULL }
837 LIST_HEAD(option_head);
838 int nr_options = 0;
840 void option_add(const char *name, unsigned int id, opt_get_cb get,
841 opt_set_cb set, opt_toggle_cb toggle)
843 struct cmus_opt *opt = xnew(struct cmus_opt, 1);
844 struct list_head *item;
846 opt->name = name;
847 opt->id = id;
848 opt->get = get;
849 opt->set = set;
850 opt->toggle = toggle;
852 item = option_head.next;
853 while (item != &option_head) {
854 struct cmus_opt *o = container_of(item, struct cmus_opt, node);
856 if (strcmp(name, o->name) < 0)
857 break;
858 item = item->next;
860 /* add before item */
861 list_add_tail(&opt->node, item);
862 nr_options++;
865 struct cmus_opt *option_find(const char *name)
867 struct cmus_opt *opt;
869 list_for_each_entry(opt, &option_head, node) {
870 if (strcmp(name, opt->name) == 0)
871 return opt;
873 error_msg("no such option %s", name);
874 return NULL;
877 void option_set(const char *name, const char *value)
879 struct cmus_opt *opt = option_find(name);
881 if (opt)
882 opt->set(opt->id, value);
885 static void get_op_option(unsigned int id, char *buf)
887 char *val = NULL;
889 player_get_op_option(id, &val);
890 if (val) {
891 strcpy(buf, val);
892 free(val);
896 static void set_op_option(unsigned int id, const char *buf)
898 int rc = player_set_op_option(id, buf);
900 if (rc) {
901 char *msg = op_get_error_msg(rc, "setting option");
902 error_msg("%s", msg);
903 free(msg);
907 /* id is ((plugin_index << 16) | option_index) */
908 static void add_op_option(unsigned int id, const char *name)
910 option_add(xstrdup(name), id, get_op_option, set_op_option, NULL);
913 void options_add(void)
915 int i;
917 /* add options */
919 for (i = 0; simple_options[i].name; i++)
920 option_add(simple_options[i].name, 0, simple_options[i].get,
921 simple_options[i].set, simple_options[i].toggle);
923 for (i = 0; i < NR_FMTS; i++)
924 option_add(str_defaults[i].name, i, get_format, set_format, NULL);
926 for (i = 0; i < NR_COLORS; i++)
927 option_add(color_names[i], i, get_color, set_color, NULL);
929 player_for_each_op_option(add_op_option);
932 static int handle_line(void *data, const char *line)
934 run_command(line);
935 return 0;
938 int source_file(const char *filename)
940 return file_for_each_line(filename, handle_line, NULL);
943 void options_load(void)
945 char filename[512];
946 int i;
948 /* initialize those that can't be statically initialized */
949 for (i = 0; str_defaults[i].name; i++)
950 option_set(str_defaults[i].name, str_defaults[i].value);
952 /* load autosave config */
953 snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
954 if (source_file(filename) == -1) {
955 const char *def = DATADIR "/cmus/rc";
957 if (errno != ENOENT)
958 error_msg("loading %s: %s", filename, strerror(errno));
960 /* load defaults */
961 if (source_file(def) == -1)
962 die_errno("loading %s", def);
965 /* load optional static config */
966 snprintf(filename, sizeof(filename), "%s/rc", cmus_config_dir);
967 if (source_file(filename) == -1) {
968 if (errno != ENOENT)
969 error_msg("loading %s: %s", filename, strerror(errno));
973 void options_exit(void)
975 struct cmus_opt *opt;
976 struct filter_entry *filt;
977 char filename[512];
978 FILE *f;
979 int i;
981 snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
982 f = fopen(filename, "w");
983 if (f == NULL) {
984 warn_errno("creating %s", filename);
985 return;
988 /* save options */
989 list_for_each_entry(opt, &option_head, node) {
990 char buf[OPTION_MAX_SIZE];
992 buf[0] = 0;
993 opt->get(opt->id, buf);
994 fprintf(f, "set %s=%s\n", opt->name, buf);
997 /* save key bindings */
998 for (i = 0; i < NR_CTXS; i++) {
999 struct binding *b = key_bindings[i];
1001 while (b) {
1002 fprintf(f, "bind %s %s %s\n", key_context_names[i], b->key->name, b->cmd);
1003 b = b->next;
1007 /* save filters */
1008 list_for_each_entry(filt, &filters_head, node)
1009 fprintf(f, "fset %s=%s\n", filt->name, filt->filter);
1010 fprintf(f, "factivate");
1011 list_for_each_entry(filt, &filters_head, node) {
1012 switch (filt->act_stat) {
1013 case FS_YES:
1014 fprintf(f, " %s", filt->name);
1015 break;
1016 case FS_NO:
1017 fprintf(f, " !%s", filt->name);
1018 break;
1021 fprintf(f, "\n");
1023 fclose(f);