moc-2.6.0~svn-r3005/0002775000076400000500000000000013710016723013131 5ustar riesebiesrcmoc-2.6.0~svn-r3005/playlist.c0000664000076400000500000005112413046745710015146 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004,2005 Damian Pietras * * 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. * * Author of title building code: Florian Kriener * * Contributors: * - Florian Kriener - title building code * - Kamil Tarkowski - plist_prev() * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #define DEBUG #include "common.h" #include "playlist.h" #include "log.h" #include "options.h" #include "files.h" #include "rbtree.h" #include "utf8.h" #include "rcc.h" /* Initial size of the table */ #define INIT_SIZE 64 void tags_free (struct file_tags *tags) { assert (tags != NULL); if (tags->title) free (tags->title); if (tags->artist) free (tags->artist); if (tags->album) free (tags->album); free (tags); } void tags_clear (struct file_tags *tags) { assert (tags != NULL); if (tags->title) free (tags->title); if (tags->artist) free (tags->artist); if (tags->album) free (tags->album); tags->title = NULL; tags->artist = NULL; tags->album = NULL; tags->track = -1; tags->time = -1; } /* Copy the tags data from src to dst freeing old fields if necessary. */ void tags_copy (struct file_tags *dst, const struct file_tags *src) { if (dst->title) free (dst->title); dst->title = xstrdup (src->title); if (dst->artist) free (dst->artist); dst->artist = xstrdup (src->artist); if (dst->album) free (dst->album); dst->album = xstrdup (src->album); dst->track = src->track; dst->time = src->time; dst->filled = src->filled; } struct file_tags *tags_new () { struct file_tags *tags; tags = (struct file_tags *)xmalloc (sizeof(struct file_tags)); tags->title = NULL; tags->artist = NULL; tags->album = NULL; tags->track = -1; tags->time = -1; tags->filled = 0; return tags; } struct file_tags *tags_dup (const struct file_tags *tags) { struct file_tags *dtags; assert (tags != NULL); dtags = tags_new(); tags_copy (dtags, tags); return dtags; } static int rb_compare (const void *a, const void *b, const void *adata) { struct plist *plist = (struct plist *)adata; int pos_a = (intptr_t)a; int pos_b = (intptr_t)b; return strcoll (plist->items[pos_a].file, plist->items[pos_b].file); } static int rb_fname_compare (const void *key, const void *data, const void *adata) { struct plist *plist = (struct plist *)adata; const char *fname = (const char *)key; const int pos = (intptr_t)data; return strcoll (fname, plist->items[pos].file); } /* Return 1 if an item has 'deleted' flag. */ inline int plist_deleted (const struct plist *plist, const int num) { assert (LIMIT(num, plist->num)); return plist->items[num].deleted; } /* Initialize the playlist. */ void plist_init (struct plist *plist) { plist->num = 0; plist->allocated = INIT_SIZE; plist->not_deleted = 0; plist->items = (struct plist_item *)xmalloc (sizeof(struct plist_item) * INIT_SIZE); plist->serial = -1; plist->search_tree = rb_tree_new (rb_compare, rb_fname_compare, plist); plist->total_time = 0; plist->items_with_time = 0; } /* Create a new playlist item with empty fields. */ struct plist_item *plist_new_item () { struct plist_item *item; item = (struct plist_item *)xmalloc (sizeof(struct plist_item)); item->file = NULL; item->type = F_OTHER; item->deleted = 0; item->title_file = NULL; item->title_tags = NULL; item->tags = NULL; item->mtime = (time_t)-1; item->queue_pos = 0; return item; } /* Add a file to the list. Return the index of the item. */ int plist_add (struct plist *plist, const char *file_name) { assert (plist != NULL); assert (plist->items != NULL); if (plist->allocated == plist->num) { plist->allocated *= 2; plist->items = (struct plist_item *)xrealloc (plist->items, sizeof(struct plist_item) * plist->allocated); } plist->items[plist->num].file = xstrdup (file_name); plist->items[plist->num].type = file_name ? file_type (file_name) : F_OTHER; plist->items[plist->num].deleted = 0; plist->items[plist->num].title_file = NULL; plist->items[plist->num].title_tags = NULL; plist->items[plist->num].tags = NULL; plist->items[plist->num].mtime = (file_name ? get_mtime(file_name) : (time_t)-1); plist->items[plist->num].queue_pos = 0; if (file_name) { rb_delete (plist->search_tree, file_name); rb_insert (plist->search_tree, (void *)(intptr_t)plist->num); } plist->num++; plist->not_deleted++; return plist->num - 1; } /* Copy all fields of item src to dst. */ void plist_item_copy (struct plist_item *dst, const struct plist_item *src) { if (dst->file) free (dst->file); dst->file = xstrdup (src->file); dst->type = src->type; dst->title_file = xstrdup (src->title_file); dst->title_tags = xstrdup (src->title_tags); dst->mtime = src->mtime; dst->queue_pos = src->queue_pos; if (src->tags) dst->tags = tags_dup (src->tags); else dst->tags = NULL; dst->deleted = src->deleted; } /* Get the pointer to the element on the playlist. * If the item number is not valid, return NULL. * Returned memory is malloced. */ char *plist_get_file (const struct plist *plist, int i) { char *file = NULL; assert (i >= 0); assert (plist != NULL); if (i < plist->num) file = xstrdup (plist->items[i].file); return file; } /* Get the number of the next item on the list (skipping deleted items). * If num == -1, get the first item. * Return -1 if there are no items left. */ int plist_next (struct plist *plist, int num) { int i = num + 1; assert (plist != NULL); assert (num >= -1); while (i < plist->num && plist->items[i].deleted) i++; return i < plist->num ? i : -1; } /* Get the number of the previous item on the list (skipping deleted items). * If num == -1, get the first item. * Return -1 if it is the beginning of the playlist. */ int plist_prev (struct plist *plist, int num) { int i = num - 1; assert (plist != NULL); assert (num >= -1); while (i >= 0 && plist->items[i].deleted) i--; return i >= 0 ? i : -1; } void plist_free_item_fields (struct plist_item *item) { if (item->file) { free (item->file); item->file = NULL; } if (item->title_tags) { free (item->title_tags); item->title_tags = NULL; } if (item->title_file) { free (item->title_file); item->title_file = NULL; } if (item->tags) { tags_free (item->tags); item->tags = NULL; } } /* Clear the list. */ void plist_clear (struct plist *plist) { int i; assert (plist != NULL); for (i = 0; i < plist->num; i++) plist_free_item_fields (&plist->items[i]); plist->items = (struct plist_item *)xrealloc (plist->items, sizeof(struct plist_item) * INIT_SIZE); plist->allocated = INIT_SIZE; plist->num = 0; plist->not_deleted = 0; rb_tree_clear (plist->search_tree); plist->total_time = 0; plist->items_with_time = 0; } /* Destroy the list freeing memory; the list can't be used after that. */ void plist_free (struct plist *plist) { assert (plist != NULL); plist_clear (plist); free (plist->items); plist->allocated = 0; plist->items = NULL; rb_tree_free (plist->search_tree); } /* Sort the playlist by file names. */ void plist_sort_fname (struct plist *plist) { struct plist_item *sorted; struct rb_node *x; int n; if (plist_count(plist) == 0) return; sorted = (struct plist_item *)xmalloc (plist_count(plist) * sizeof(struct plist_item)); x = rb_min (plist->search_tree); assert (!rb_is_null(x)); while (plist_deleted(plist, (intptr_t)rb_get_data (x))) x = rb_next (x); sorted[0] = plist->items[(intptr_t)rb_get_data (x)]; rb_set_data (x, NULL); n = 1; while (!rb_is_null(x = rb_next(x))) { if (!plist_deleted(plist, (intptr_t)rb_get_data (x))) { sorted[n] = plist->items[(intptr_t)rb_get_data (x)]; rb_set_data (x, (void *)(intptr_t)n++); } } plist->num = n; plist->not_deleted = n; memcpy (plist->items, sorted, sizeof(struct plist_item) * n); free (sorted); } /* Find an item in the list. Return the index or -1 if not found. */ int plist_find_fname (struct plist *plist, const char *file) { struct rb_node *x; assert (plist != NULL); assert (file != NULL); x = rb_search (plist->search_tree, file); if (rb_is_null(x)) return -1; return !plist_deleted(plist, (intptr_t)rb_get_data (x)) ? (intptr_t)rb_get_data (x) : -1; } /* Find an item in the list; also find deleted items. If there is more than * one item for this file, return the non-deleted one or, if all are deleted, * return the last of them. Return the index or -1 if not found. */ int plist_find_del_fname (const struct plist *plist, const char *file) { int i; int item = -1; assert (plist != NULL); for (i = 0; i < plist->num; i++) { if (plist->items[i].file && !strcmp(plist->items[i].file, file)) { if (item == -1 || plist_deleted(plist, item)) item = i; } } return item; } /* Returns the next filename that is a dead entry, or NULL if there are none * left. * * It will set the index on success. */ const char *plist_get_next_dead_entry (const struct plist *plist, int *last_index) { int i; assert (last_index != NULL); assert (plist != NULL); for (i = *last_index; i < plist->num; i++) { if (plist->items[i].file && ! plist_deleted(plist, i) && ! can_read_file(plist->items[i].file)) { *last_index = i + 1; return plist->items[i].file; } } return NULL; } #define if_not_empty(str) (tags && (str) && (*str) ? (str) : NULL) static char *title_expn_subs(char fmt, const struct file_tags *tags) { static char track[16]; switch (fmt) { case 'n': if (!tags || tags->track == -1) break; snprintf (track, sizeof(track), "%d", tags->track); return track; case 'a': return if_not_empty (tags->artist); case 'A': return if_not_empty (tags->album); case 't': return if_not_empty (tags->title); default: fatal ("Error parsing format string!"); } return NULL; } static inline void check_zero (const char *x) { if (*x == '\0') fatal ("Unexpected end of title expression!"); } /* Generate a title from fmt. */ static void do_title_expn (char *dest, int size, const char *fmt, const struct file_tags *tags) { const char *h; int free = --size; short escape = 0; dest[0] = 0; while (free > 0 && *fmt) { if (*fmt == '%' && !escape) { check_zero(++fmt); /* do ternary expansion * format: %(x:true:false) */ if (*fmt == '(') { char separator, expr[256]; int expr_pos = 0; check_zero(++fmt); h = title_expn_subs(*fmt, tags); check_zero(++fmt); separator = *fmt; check_zero(++fmt); if(h) { /* true */ /* copy the expression */ while (escape || *fmt != separator) { if (expr_pos == sizeof(expr)-2) fatal ("Nested ternary expression too long!"); expr[expr_pos++] = *fmt; if (*fmt == '\\') escape = 1; else escape = 0; check_zero(++fmt); } expr[expr_pos] = '\0'; /* eat the rest */ while (escape || *fmt != ')') { if (escape) escape = 0; else if (*fmt == '\\') escape = 1; check_zero(++fmt); } } else { /* false */ /* eat the truth :-) */ while (escape || *fmt != separator) { if (escape) escape = 0; else if (*fmt == '\\') escape = 1; check_zero(++fmt); } check_zero(++fmt); /* copy the expression */ while (escape || *fmt != ')') { if (expr_pos == sizeof(expr)-2) fatal ("Ternary expression too long!"); expr[expr_pos++] = *fmt; if (*fmt == '\\') escape = 1; else escape = 0; check_zero(++fmt); } expr[expr_pos] = '\0'; } do_title_expn((dest + size - free), free, expr, tags); free -= strlen(dest + size - free); } else { h = title_expn_subs(*fmt, tags); if (h) { strncat(dest, h, free-1); free -= strlen (h); } } } else if (*fmt == '\\' && !escape) escape = 1; else { dest[size - free] = *fmt; dest[size - free + 1] = 0; --free; escape = 0; } fmt++; } free = MAX(free, 0); /* Possible integer overflow? */ dest[size - free] = '\0'; } /* Build file title from struct file_tags. Returned memory is malloc()ed. */ char *build_title_with_format (const struct file_tags *tags, const char *fmt) { char title[512]; do_title_expn (title, sizeof(title), fmt, tags); return xstrdup (title); } /* Build file title from struct file_tags. Returned memory is malloc()ed. */ char *build_title (const struct file_tags *tags) { return build_title_with_format (tags, options_get_str ("FormatString")); } /* Copy the item to the playlist. Return the index of the added item. */ int plist_add_from_item (struct plist *plist, const struct plist_item *item) { int pos = plist_add (plist, item->file); plist_item_copy (&plist->items[pos], item); if (item->tags && item->tags->time != -1) { plist->total_time += item->tags->time; plist->items_with_time++; } return pos; } void plist_delete (struct plist *plist, const int num) { assert (plist != NULL); assert (!plist->items[num].deleted); assert (plist->not_deleted > 0); if (num < plist->num) { /* Free every field except the file, it is needed in deleted * items. */ char *file = plist->items[num].file; plist->items[num].file = NULL; if (plist->items[num].tags && plist->items[num].tags->time != -1) { plist->total_time -= plist->items[num].tags->time; plist->items_with_time--; } plist_free_item_fields (&plist->items[num]); plist->items[num].file = file; plist->items[num].deleted = 1; plist->not_deleted--; } } /* Count non-deleted items. */ int plist_count (const struct plist *plist) { assert (plist != NULL); return plist->not_deleted; } /* Set tags title of an item. */ void plist_set_title_tags (struct plist *plist, const int num, const char *title) { assert (LIMIT(num, plist->num)); if (plist->items[num].title_tags) free (plist->items[num].title_tags); plist->items[num].title_tags = xstrdup (title); } /* Set file title of an item. */ void plist_set_title_file (struct plist *plist, const int num, const char *title) { assert (LIMIT(num, plist->num)); if (plist->items[num].title_file) free (plist->items[num].title_file); #ifdef HAVE_RCC if (options_get_bool ("UseRCCForFilesystem")) { char *t_str = xstrdup (title); plist->items[num].title_file = rcc_reencode (t_str); return; } #endif plist->items[num].title_file = xstrdup (title); } /* Set file for an item. */ void plist_set_file (struct plist *plist, const int num, const char *file) { assert (LIMIT(num, plist->num)); assert (file != NULL); if (plist->items[num].file) { rb_delete (plist->search_tree, file); free (plist->items[num].file); } plist->items[num].file = xstrdup (file); plist->items[num].type = file_type (file); plist->items[num].mtime = get_mtime (file); rb_insert (plist->search_tree, (void *)(intptr_t)num); } /* Add the content of playlist b to a by copying items. */ void plist_cat (struct plist *a, struct plist *b) { int i; assert (a != NULL); assert (b != NULL); for (i = 0; i < b->num; i++) { if (plist_deleted (b, i)) continue; assert (b->items[i].file != NULL); if (plist_find_fname (a, b->items[i].file) == -1) plist_add_from_item (a, &b->items[i]); } } /* Set the time tags field for the item. */ void plist_set_item_time (struct plist *plist, const int num, const int time) { int old_time; assert (plist != NULL); assert (LIMIT(num, plist->num)); if (!plist->items[num].tags) { plist->items[num].tags = tags_new (); old_time = -1; } else if (plist->items[num].tags->time != -1) old_time = plist->items[num].tags->time; else old_time = -1; if (old_time != -1) { plist->total_time -= old_time; plist->items_with_time--; } if (time != -1) { plist->total_time += time; plist->items_with_time++; } plist->items[num].tags->time = time; plist->items[num].tags->filled |= TAGS_TIME; } int get_item_time (const struct plist *plist, const int i) { assert (plist != NULL); if (plist->items[i].tags) return plist->items[i].tags->time; return -1; } /* Return the total time of all files on the playlist having the time tag. * If the time information is missing for any file, all_files is set to 0, * otherwise 1. * Returned value is that counted by plist_count_time(), so may be not * up-to-date. */ int plist_total_time (const struct plist *plist, int *all_files) { *all_files = plist->not_deleted == plist->items_with_time; return plist->total_time; } /* Swap two items on the playlist. */ static void plist_swap (struct plist *plist, const int a, const int b) { assert (plist != NULL); assert (LIMIT(a, plist->num)); assert (LIMIT(b, plist->num)); if (a != b) { struct plist_item t; t = plist->items[a]; plist->items[a] = plist->items[b]; plist->items[b] = t; } } /* Shuffle the playlist. */ void plist_shuffle (struct plist *plist) { int i; for (i = 0; i < plist->num; i += 1) plist_swap (plist, i, (rand () / (float)RAND_MAX) * (plist->num - 1)); rb_tree_clear (plist->search_tree); for (i = 0; i < plist->num; i++) rb_insert (plist->search_tree, (void *)(intptr_t)i); } /* Swap the first item on the playlist with the item with file fname. */ void plist_swap_first_fname (struct plist *plist, const char *fname) { int i; assert (plist != NULL); assert (fname != NULL); i = plist_find_fname (plist, fname); if (i != -1 && i != 0) { rb_delete (plist->search_tree, fname); rb_delete (plist->search_tree, plist->items[0].file); plist_swap (plist, 0, i); rb_insert (plist->search_tree, NULL); rb_insert (plist->search_tree, (void *)(intptr_t)i); } } void plist_set_serial (struct plist *plist, const int serial) { plist->serial = serial; } int plist_get_serial (const struct plist *plist) { return plist->serial; } /* Return the index of the last non-deleted item from the playlist. * Return -1 if there are no items. */ int plist_last (const struct plist *plist) { int i; i = plist->num - 1; while (i > 0 && plist_deleted(plist, i)) i--; return i; } enum file_type plist_file_type (const struct plist *plist, const int num) { assert (plist != NULL); assert (num < plist->num); return plist->items[num].type; } /* Remove items from playlist 'a' that are also present on playlist 'b'. */ void plist_remove_common_items (struct plist *a, struct plist *b) { int i; assert (a != NULL); assert (b != NULL); for (i = 0; i < a->num; i += 1) { if (plist_deleted (a, i)) continue; assert (a->items[i].file != NULL); if (plist_find_fname (b, a->items[i].file) != -1) plist_delete (a, i); } } void plist_discard_tags (struct plist *plist) { int i; assert (plist != NULL); for (i = 0; i < plist->num; i++) if (!plist_deleted(plist, i) && plist->items[i].tags) { tags_free (plist->items[i].tags); plist->items[i].tags = NULL; } plist->items_with_time = 0; plist->total_time = 0; } void plist_set_tags (struct plist *plist, const int num, const struct file_tags *tags) { int old_time; assert (plist != NULL); assert (LIMIT(num, plist->num)); assert (tags != NULL); if (plist->items[num].tags && plist->items[num].tags->time != -1) old_time = plist->items[num].tags->time; else old_time = -1; if (plist->items[num].tags) tags_free (plist->items[num].tags); plist->items[num].tags = tags_dup (tags); if (old_time != -1) { plist->total_time -= old_time; plist->items_with_time--; } if (tags->time != -1) { plist->total_time += tags->time; plist->items_with_time++; } } struct file_tags *plist_get_tags (const struct plist *plist, const int num) { assert (plist != NULL); assert (LIMIT(num, plist->num)); if (plist->items[num].tags) return tags_dup (plist->items[num].tags); return NULL; } /* Swap two files on the playlist. */ void plist_swap_files (struct plist *plist, const char *file1, const char *file2) { struct rb_node *x1, *x2; assert (plist != NULL); assert (file1 != NULL); assert (file2 != NULL); x1 = rb_search (plist->search_tree, file1); x2 = rb_search (plist->search_tree, file2); if (!rb_is_null(x1) && !rb_is_null(x2)) { const void *t; plist_swap (plist, (intptr_t)rb_get_data (x1), (intptr_t)rb_get_data (x2)); t = rb_get_data (x1); rb_set_data (x1, rb_get_data (x2)); rb_set_data (x2, t); } } /* Return the position of a file in the list, starting with 1. */ int plist_get_position (const struct plist *plist, int num) { int i, pos = 1; assert (LIMIT(num, plist->num)); for (i = 0; i < num; i++) { if(!plist->items[i].deleted) pos++; } return pos; } moc-2.6.0~svn-r3005/AUTHORS0000664000076400000500000000004110123261013014157 0ustar riesebiesrcDamian Pietras moc-2.6.0~svn-r3005/playlist.h0000664000076400000500000000771412424322506015152 0ustar riesebiesrc#ifndef PLAYLIST_H #define PLAYLIST_H #include #include "rbtree.h" #ifdef __cplusplus extern "C" { #endif /* Flags for the info decoder function. */ enum tags_select { TAGS_COMMENTS = 0x01, /* artist, title, etc. */ TAGS_TIME = 0x02 /* time of the file. */ }; struct file_tags { char *title; char *artist; char *album; int track; int time; int filled; /* Which tags are filled: TAGS_COMMENTS, TAGS_TIME. */ }; enum file_type { F_DIR, F_SOUND, F_URL, F_PLAYLIST, F_THEME, F_OTHER }; struct plist_item { char *file; enum file_type type; /* type of the file (F_OTHER if not read yet) */ char *title_file; /* title based on the file name */ char *title_tags; /* title based on the tags */ struct file_tags *tags; short deleted; time_t mtime; /* modification time */ int queue_pos; /* position in the queue */ }; struct plist { int num; /* Number of elements on the list */ int allocated; /* Number of allocated elements */ int not_deleted; /* Number of non-deleted items */ struct plist_item *items; int serial; /* Optional serial number of this playlist */ int total_time; /* Total time for files on the playlist */ int items_with_time; /* Number of items for which the time is set. */ struct rb_tree *search_tree; }; void plist_init (struct plist *plist); int plist_add (struct plist *plist, const char *file_name); int plist_add_from_item (struct plist *plist, const struct plist_item *item); char *plist_get_file (const struct plist *plist, int i); int plist_next (struct plist *plist, int num); int plist_prev (struct plist *plist, int num); void plist_clear (struct plist *plist); void plist_delete (struct plist *plist, const int num); void plist_free (struct plist *plist); void plist_sort_fname (struct plist *plist); int plist_find_fname (struct plist *plist, const char *file); struct file_tags *tags_new (); void tags_clear (struct file_tags *tags); void tags_copy (struct file_tags *dst, const struct file_tags *src); struct file_tags *tags_dup (const struct file_tags *tags); void tags_free (struct file_tags *tags); char *build_title_with_format (const struct file_tags *tags, const char *fmt); char *build_title (const struct file_tags *tags); int plist_count (const struct plist *plist); void plist_set_title_tags (struct plist *plist, const int num, const char *title); void plist_set_title_file (struct plist *plist, const int num, const char *title); void plist_set_file (struct plist *plist, const int num, const char *file); int plist_deleted (const struct plist *plist, const int num); void plist_cat (struct plist *a, struct plist *b); void update_file (struct plist_item *item); void plist_set_item_time (struct plist *plist, const int num, const int time); int get_item_time (const struct plist *plist, const int i); int plist_total_time (const struct plist *plisti, int *all_files); void plist_shuffle (struct plist *plist); void plist_swap_first_fname (struct plist *plist, const char *fname); struct plist_item *plist_new_item (); void plist_free_item_fields (struct plist_item *item); void plist_set_serial (struct plist *plist, const int serial); int plist_get_serial (const struct plist *plist); int plist_last (const struct plist *plist); int plist_find_del_fname (const struct plist *plist, const char *file); const char *plist_get_next_dead_entry (const struct plist *plist, int *last_index); void plist_item_copy (struct plist_item *dst, const struct plist_item *src); enum file_type plist_file_type (const struct plist *plist, const int num); void plist_remove_common_items (struct plist *a, struct plist *b); void plist_discard_tags (struct plist *plist); void plist_set_tags (struct plist *plist, const int num, const struct file_tags *tags); struct file_tags *plist_get_tags (const struct plist *plist, const int num); void plist_swap_files (struct plist *plist, const char *file1, const char *file2); int plist_get_position (const struct plist *plist, int num); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/COPYING0000664000076400000500000004310312032123343014154 0ustar riesebiesrc GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. moc-2.6.0~svn-r3005/tags_cache.c0000664000076400000500000007033013013765710015362 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005, 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DB_H # ifndef HAVE_U_INT typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef unsigned long int u_long; # endif # include # define STRERROR_FN bdb_strerror #endif #define DEBUG #include "common.h" #include "server.h" #include "playlist.h" #include "rbtree.h" #include "files.h" #include "tags_cache.h" #include "log.h" #include "audio.h" #ifdef HAVE_DB_H # define DB_ONLY #else # define DB_ONLY ATTR_UNUSED #endif /* The name of the tags database in the cache directory. */ #define TAGS_DB "tags.db" /* The name of the version tag file in the cache directory. */ #define MOC_VERSION_TAG "moc_version_tag" /* The maximum length of the version tag (including trailing NULL). */ #define VERSION_TAG_MAX 64 /* Number used to create cache version tag to detect incompatibilities * between cache version stored on the disk and MOC/BerkeleyDB environment. * * If you modify the DB structure, increase this number. You can also * temporarily set it to zero to disable cache activity during structural * changes which require multiple commits. */ #define CACHE_DB_FORMAT_VERSION 1 /* How frequently to flush the tags database to disk. A value of zero * disables flushing. */ #define DB_SYNC_COUNT 5 /* Element of a requests queue. */ struct request_queue_node { struct request_queue_node *next; char *file; /* file that this request is for (malloc()ed) */ int tags_sel; /* which tags to read (TAGS_*) */ }; struct request_queue { struct request_queue_node *head; struct request_queue_node *tail; }; struct tags_cache { /* BerkeleyDB's stuff for storing cache. */ #ifdef HAVE_DB_H DB_ENV *db_env; DB *db; u_int32_t locker; #endif int max_items; /* maximum number of items in the cache. */ struct request_queue queues[CLIENTS_MAX]; /* requests queues for each client */ int stop_reader_thread; /* request for stopping read thread (if non-zero) */ pthread_cond_t request_cond; /* condition for signalizing new requests */ pthread_mutex_t mutex; /* mutex for all above data (except db because it's thread-safe) */ pthread_t reader_thread; /* tid of the reading thread */ }; struct cache_record { time_t mod_time; /* last modification time of the file */ time_t atime; /* Time of last access. */ struct file_tags *tags; }; /* BerkleyDB-provided error code to description function wrapper. */ #ifdef HAVE_DB_H static inline char *bdb_strerror (int errnum) { char *result; if (errnum > 0) result = xstrerror (errnum); else result = xstrdup (db_strerror (errnum)); return result; } #endif static void request_queue_init (struct request_queue *q) { assert (q != NULL); q->head = NULL; q->tail = NULL; } static void request_queue_clear (struct request_queue *q) { assert (q != NULL); while (q->head) { struct request_queue_node *o = q->head; q->head = q->head->next; free (o->file); free (o); } q->tail = NULL; } /* Remove items from the queue from the beginning to the specified file. */ static void request_queue_clear_up_to (struct request_queue *q, const char *file) { int stop = 0; assert (q != NULL); while (q->head && !stop) { struct request_queue_node *o = q->head; q->head = q->head->next; if (!strcmp (o->file, file)) stop = 1; free (o->file); free (o); } if (!q->head) q->tail = NULL; } static void request_queue_add (struct request_queue *q, const char *file, int tags_sel) { assert (q != NULL); if (!q->head) { q->head = (struct request_queue_node *)xmalloc ( sizeof(struct request_queue_node)); q->tail = q->head; } else { assert (q->tail != NULL); assert (q->tail->next == NULL); q->tail->next = (struct request_queue_node *)xmalloc ( sizeof(struct request_queue_node)); q->tail = q->tail->next; } q->tail->file = xstrdup (file); q->tail->tags_sel = tags_sel; q->tail->next = NULL; } static int request_queue_empty (const struct request_queue *q) { assert (q != NULL); return q->head == NULL; } /* Get the file name of the first element in the queue or NULL if the queue is * empty. Put tags to be read in *tags_sel. Returned memory is malloc()ed. */ static char *request_queue_pop (struct request_queue *q, int *tags_sel) { struct request_queue_node *n; char *file; assert (q != NULL); if (q->head == NULL) return NULL; n = q->head; q->head = n->next; file = n->file; *tags_sel = n->tags_sel; free (n); if (q->tail == n) q->tail = NULL; /* the queue is empty */ return file; } #ifdef HAVE_DB_H static size_t strlen_null (const char *s) { return s ? strlen (s) : 0; } #endif #ifdef HAVE_DB_H static char *cache_record_serialize (const struct cache_record *rec, int *len) { char *buf; char *p; size_t artist_len; size_t album_len; size_t title_len; artist_len = strlen_null (rec->tags->artist); album_len = strlen_null (rec->tags->album); title_len = strlen_null (rec->tags->title); *len = sizeof(rec->mod_time) + sizeof(rec->atime) + sizeof(size_t) * 3 /* lengths of title, artist, time. */ + artist_len + album_len + title_len + sizeof(rec->tags->track) + sizeof(rec->tags->time); buf = p = (char *)xmalloc (*len); memcpy (p, &rec->mod_time, sizeof(rec->mod_time)); p += sizeof(rec->mod_time); memcpy (p, &rec->atime, sizeof(rec->atime)); p += sizeof(rec->atime); memcpy (p, &artist_len, sizeof(artist_len)); p += sizeof(artist_len); if (artist_len) { memcpy (p, rec->tags->artist, artist_len); p += artist_len; } memcpy (p, &album_len, sizeof(album_len)); p += sizeof(album_len); if (album_len) { memcpy (p, rec->tags->album, album_len); p += album_len; } memcpy (p, &title_len, sizeof(title_len)); p += sizeof(title_len); if (title_len) { memcpy (p, rec->tags->title, title_len); p += title_len; } memcpy (p, &rec->tags->track, sizeof(rec->tags->track)); p += sizeof(rec->tags->track); memcpy (p, &rec->tags->time, sizeof(rec->tags->time)); p += sizeof(rec->tags->time); return buf; } #endif #ifdef HAVE_DB_H static int cache_record_deserialize (struct cache_record *rec, const char *serialized, size_t size, int skip_tags) { const char *p = serialized; size_t bytes_left = size; size_t str_len; assert (rec != NULL); assert (serialized != NULL); if (!skip_tags) rec->tags = tags_new (); else rec->tags = NULL; #define extract_num(var) \ do { \ if (bytes_left < sizeof(var)) \ goto err; \ memcpy (&var, p, sizeof(var)); \ bytes_left -= sizeof(var); \ p += sizeof(var); \ } while (0) #define extract_str(var) \ do { \ if (bytes_left < sizeof(str_len)) \ goto err; \ memcpy (&str_len, p, sizeof(str_len)); \ p += sizeof(str_len); \ if (bytes_left < str_len) \ goto err; \ var = xmalloc (str_len + 1); \ memcpy (var, p, str_len); \ var[str_len] = '\0'; \ p += str_len; \ } while (0) extract_num (rec->mod_time); extract_num (rec->atime); if (!skip_tags) { extract_str (rec->tags->artist); extract_str (rec->tags->album); extract_str (rec->tags->title); extract_num (rec->tags->track); extract_num (rec->tags->time); if (rec->tags->title) rec->tags->filled |= TAGS_COMMENTS; else { if (rec->tags->artist) free (rec->tags->artist); rec->tags->artist = NULL; if (rec->tags->album) free (rec->tags->album); rec->tags->album = NULL; } if (rec->tags->time >= 0) rec->tags->filled |= TAGS_TIME; } return 1; err: logit ("Cache record deserialization error at %tdB", p - serialized); tags_free (rec->tags); rec->tags = NULL; return 0; } #endif /* Locked DB function prototype. * The function must not acquire or release DB locks. */ #ifdef HAVE_DB_H typedef void *t_locked_fn (struct tags_cache *, const char *, int, int, DBT *, DBT *); #endif /* This function ensures that a DB function takes place while holding a * database record lock. It also provides an initialised database thang * for the key and record. */ #ifdef HAVE_DB_H static void *with_db_lock (t_locked_fn fn, struct tags_cache *c, const char *file, int tags_sel, int client_id) { int rc; void *result; DB_LOCK lock; DBT key, record; assert (c->db_env != NULL); memset (&key, 0, sizeof (key)); key.data = (void *) file; key.size = strlen (file); memset (&record, 0, sizeof (record)); record.flags = DB_DBT_MALLOC; rc = c->db_env->lock_get (c->db_env, c->locker, 0, &key, DB_LOCK_WRITE, &lock); if (rc) fatal ("Can't get DB lock: %s", db_strerror (rc)); result = fn (c, file, tags_sel, client_id, &key, &record); rc = c->db_env->lock_put (c->db_env, &lock); if (rc) fatal ("Can't release DB lock: %s", db_strerror (rc)); if (record.data) free (record.data); return result; } #endif #ifdef HAVE_DB_H static void tags_cache_remove_rec (struct tags_cache *c, const char *fname) { DBT key; int ret; assert (fname != NULL); debug ("Removing %s from the cache...", fname); memset (&key, 0, sizeof(key)); key.data = (void *)fname; key.size = strlen (fname); ret = c->db->del (c->db, NULL, &key, 0); if (ret) logit ("Can't remove item for %s from the cache: %s", fname, db_strerror (ret)); } #endif /* Remove the one element of the cache based on it's access time. */ #ifdef HAVE_DB_H static void tags_cache_gc (struct tags_cache *c) { DBC *cur; DBT key; DBT serialized_cache_rec; int ret; char *last_referenced = NULL; time_t last_referenced_atime = time (NULL) + 1; int nitems = 0; c->db->cursor (c->db, NULL, &cur, 0); memset (&key, 0, sizeof(key)); memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec)); key.flags = DB_DBT_MALLOC; serialized_cache_rec.flags = DB_DBT_MALLOC; while (true) { struct cache_record rec; #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6 ret = cur->c_get (cur, &key, &serialized_cache_rec, DB_NEXT); #else ret = cur->get (cur, &key, &serialized_cache_rec, DB_NEXT); #endif if (ret != 0) break; if (cache_record_deserialize (&rec, serialized_cache_rec.data, serialized_cache_rec.size, 1) && rec.atime < last_referenced_atime) { last_referenced_atime = rec.atime; if (last_referenced) free (last_referenced); last_referenced = (char *)xmalloc (key.size + 1); memcpy (last_referenced, key.data, key.size); last_referenced[key.size] = '\0'; } // TODO: remove objects with serialization error. nitems++; free (key.data); free (serialized_cache_rec.data); } if (ret != DB_NOTFOUND) log_errno ("Searching for element to remove failed (cursor)", ret); #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6 cur->c_close (cur); #else cur->close (cur); #endif debug ("Elements in cache: %d (limit %d)", nitems, c->max_items); if (last_referenced) { if (nitems >= c->max_items) tags_cache_remove_rec (c, last_referenced); free (last_referenced); } else debug ("Cache empty"); } #endif /* Synchronize cache every DB_SYNC_COUNT updates. */ #ifdef HAVE_DB_H static void tags_cache_sync (struct tags_cache *c) { static int sync_count = 0; if (DB_SYNC_COUNT == 0) return; sync_count += 1; if (sync_count >= DB_SYNC_COUNT) { sync_count = 0; c->db->sync (c->db, 0); } } #endif /* Add this tags object for the file to the cache. */ #ifdef HAVE_DB_H static void tags_cache_add (struct tags_cache *c, const char *file, DBT *key, struct file_tags *tags) { char *serialized_cache_rec; int serial_len; struct cache_record rec; DBT data; int ret; assert (tags != NULL); debug ("Adding/updating cache object"); rec.mod_time = get_mtime (file); rec.atime = time (NULL); rec.tags = tags; serialized_cache_rec = cache_record_serialize (&rec, &serial_len); if (!serialized_cache_rec) return; memset (&data, 0, sizeof(data)); data.data = serialized_cache_rec; data.size = serial_len; tags_cache_gc (c); ret = c->db->put (c->db, NULL, key, &data, 0); if (ret) error_errno ("DB put error", ret); tags_cache_sync (c); free (serialized_cache_rec); } #endif /* Read time tags for a file into tags structure (or create it if NULL). */ struct file_tags *read_missing_tags (const char *file, struct file_tags *tags, int tags_sel) { if (tags == NULL) tags = tags_new (); if (tags_sel & TAGS_TIME) { int time; /* Try to get it from the server's playlist first. */ time = audio_get_ftime (file); if (time != -1) { tags->time = time; tags->filled |= TAGS_TIME; tags_sel &= ~TAGS_TIME; } } tags = read_file_tags (file, tags, tags_sel); return tags; } /* Read the selected tags for this file and add it to the cache. */ #ifdef HAVE_DB_H static void *locked_read_add (struct tags_cache *c, const char *file, const int tags_sel, const int client_id, DBT *key, DBT *serialized_cache_rec) { int ret; struct file_tags *tags = NULL; assert (c->db != NULL); ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0); if (ret && ret != DB_NOTFOUND) log_errno ("Cache DB get error", ret); /* If this entry is already present in the cache, we have 3 options: * we must read different tags (TAGS_*) or the tags are outdated * or this is an immediate tags read (client_id == -1) */ if (ret == 0) { struct cache_record rec; if (cache_record_deserialize (&rec, serialized_cache_rec->data, serialized_cache_rec->size, 0)) { time_t curr_mtime = get_mtime (file); if (rec.mod_time != curr_mtime) { debug ("Tags in the cache are outdated"); tags_free (rec.tags); /* remove them and reread tags */ } else if ((rec.tags->filled & tags_sel) == tags_sel && client_id == -1) { debug ("Tags are in the cache."); return rec.tags; } else { debug ("Tags in the cache are not what we want"); tags = rec.tags; /* read additional tags */ } } } tags = read_missing_tags (file, tags, tags_sel); tags_cache_add (c, file, key, tags); return tags; } #endif /* Read the selected tags for this file and add it to the cache. * If client_id != -1, the server is notified using tags_response(). * If client_id == -1, copy of file_tags is returned. */ static struct file_tags *tags_cache_read_add (struct tags_cache *c DB_ONLY, const char *file, int tags_sel, int client_id) { struct file_tags *tags = NULL; assert (file != NULL); debug ("Getting tags for %s", file); #ifdef HAVE_DB_H if (c->max_items) tags = (struct file_tags *)with_db_lock (locked_read_add, c, file, tags_sel, client_id); else #endif tags = read_missing_tags (file, tags, tags_sel); if (client_id != -1) { tags_response (client_id, file, tags); tags_free (tags); tags = NULL; } /* TODO: Remove the oldest items from the cache if we exceeded the maximum * cache size */ return tags; } static void *reader_thread (void *cache_ptr) { struct tags_cache *c; int curr_queue = 0; /* index of the queue from where we will get the next request */ logit ("Tags reader thread started"); assert (cache_ptr != NULL); c = (struct tags_cache *)cache_ptr; LOCK (c->mutex); while (!c->stop_reader_thread) { int i; char *request_file; int tags_sel = 0; /* Find the queue with a request waiting. Begin searching at * curr_queue: we want to get one request from each queue, * and then move to the next non-empty queue. */ i = curr_queue; while (i < CLIENTS_MAX && request_queue_empty (&c->queues[i])) i++; if (i == CLIENTS_MAX) { i = 0; while (i < curr_queue && request_queue_empty (&c->queues[i])) i++; if (i == curr_queue) { debug ("All queues empty, waiting"); pthread_cond_wait (&c->request_cond, &c->mutex); continue; } } curr_queue = i; request_file = request_queue_pop (&c->queues[curr_queue], &tags_sel); UNLOCK (c->mutex); tags_cache_read_add (c, request_file, tags_sel, curr_queue); free (request_file); LOCK (c->mutex); curr_queue = (curr_queue + 1) % CLIENTS_MAX; } UNLOCK (c->mutex); logit ("Exiting tags reader thread"); return NULL; } struct tags_cache *tags_cache_new (size_t max_size) { int i, rc; struct tags_cache *result; result = (struct tags_cache *)xmalloc (sizeof (struct tags_cache)); #ifdef HAVE_DB_H result->db_env = NULL; result->db = NULL; #endif for (i = 0; i < CLIENTS_MAX; i++) request_queue_init (&result->queues[i]); #if CACHE_DB_FORMAT_VERSION result->max_items = max_size; #else result->max_items = 0; #endif result->stop_reader_thread = 0; pthread_mutex_init (&result->mutex, NULL); rc = pthread_cond_init (&result->request_cond, NULL); if (rc != 0) fatal ("Can't create request_cond: %s", xstrerror (rc)); rc = pthread_create (&result->reader_thread, NULL, reader_thread, result); if (rc != 0) fatal ("Can't create tags cache thread: %s", xstrerror (rc)); return result; } void tags_cache_free (struct tags_cache *c) { int i, rc; assert (c != NULL); LOCK (c->mutex); c->stop_reader_thread = 1; pthread_cond_signal (&c->request_cond); UNLOCK (c->mutex); #ifdef HAVE_DB_H if (c->db) { #ifndef NDEBUG c->db->set_errcall (c->db, NULL); c->db->set_msgcall (c->db, NULL); c->db->set_paniccall (c->db, NULL); #endif c->db->close (c->db, 0); c->db = NULL; } #endif #ifdef HAVE_DB_H if (c->db_env) { c->db_env->lock_id_free (c->db_env, c->locker); #ifndef NDEBUG c->db_env->set_errcall (c->db_env, NULL); c->db_env->set_msgcall (c->db_env, NULL); c->db_env->set_paniccall (c->db_env, NULL); #endif c->db_env->close (c->db_env, 0); c->db_env = NULL; } #endif rc = pthread_join (c->reader_thread, NULL); if (rc != 0) fatal ("pthread_join() on cache reader thread failed: %s", xstrerror (rc)); for (i = 0; i < CLIENTS_MAX; i++) request_queue_clear (&c->queues[i]); rc = pthread_mutex_destroy (&c->mutex); if (rc != 0) log_errno ("Can't destroy mutex", rc); rc = pthread_cond_destroy (&c->request_cond); if (rc != 0) log_errno ("Can't destroy request_cond", rc); free (c); } #ifdef HAVE_DB_H static void *locked_add_request (struct tags_cache *c, const char *file, int tags_sel, int client_id, DBT *key, DBT *serialized_cache_rec) { int db_ret; struct cache_record rec; assert (c->db); db_ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0); if (db_ret == DB_NOTFOUND) return NULL; if (db_ret) { error_errno ("Cache DB search error", db_ret); return NULL; } if (cache_record_deserialize (&rec, serialized_cache_rec->data, serialized_cache_rec->size, 0)) { if (rec.mod_time == get_mtime (file) && (rec.tags->filled & tags_sel) == tags_sel) { tags_response (client_id, file, rec.tags); tags_free (rec.tags); debug ("Tags are present in the cache"); return (void *)1; } tags_free (rec.tags); debug ("Found outdated or incomplete tags in the cache"); } return NULL; } #endif void tags_cache_add_request (struct tags_cache *c, const char *file, int tags_sel, int client_id) { void *rc = NULL; assert (c != NULL); assert (file != NULL); assert (LIMIT(client_id, CLIENTS_MAX)); debug ("Request for tags for '%s' from client %d", file, client_id); #ifdef HAVE_DB_H if (c->max_items) rc = with_db_lock (locked_add_request, c, file, tags_sel, client_id); #endif if (!rc) { LOCK (c->mutex); request_queue_add (&c->queues[client_id], file, tags_sel); pthread_cond_signal (&c->request_cond); UNLOCK (c->mutex); } } void tags_cache_clear_queue (struct tags_cache *c, int client_id) { assert (c != NULL); assert (LIMIT(client_id, CLIENTS_MAX)); LOCK (c->mutex); request_queue_clear (&c->queues[client_id]); debug ("Cleared requests queue for client %d", client_id); UNLOCK (c->mutex); } /* Remove all pending requests from the queue for the given client up to * the request associated with the given file. */ void tags_cache_clear_up_to (struct tags_cache *c, const char *file, int client_id) { assert (c != NULL); assert (LIMIT(client_id, CLIENTS_MAX)); assert (file != NULL); LOCK (c->mutex); debug ("Removing requests for client %d up to file %s", client_id, file); request_queue_clear_up_to (&c->queues[client_id], file); UNLOCK (c->mutex); } #if defined(HAVE_DB_H) && !defined(NDEBUG) static void db_err_cb (const DB_ENV *unused ATTR_UNUSED, const char *errpfx, const char *msg) { assert (msg); if (errpfx && errpfx[0]) logit ("BDB said: %s: %s", errpfx, msg); else logit ("BDB said: %s", msg); } #endif #if defined(HAVE_DB_H) && !defined(NDEBUG) static void db_msg_cb (const DB_ENV *unused ATTR_UNUSED, const char *msg) { assert (msg); logit ("BDB said: %s", msg); } #endif #if defined(HAVE_DB_H) && !defined(NDEBUG) static void db_panic_cb (DB_ENV *unused ATTR_UNUSED, int errval) { log_errno ("BDB said", errval); } #endif /* Purge content of a directory. */ #ifdef HAVE_DB_H static int purge_directory (const char *dir_path) { DIR *dir; struct dirent *d; logit ("Purging %s...", dir_path); dir = opendir (dir_path); if (!dir) { char *err = xstrerror (errno); logit ("Can't open directory %s: %s", dir_path, err); free (err); return 0; } while ((d = readdir (dir))) { struct stat st; char *fpath; int len; if (!strcmp (d->d_name, ".") || !strcmp (d->d_name, "..")) continue; len = strlen (dir_path) + strlen (d->d_name) + 2; fpath = (char *)xmalloc (len); snprintf (fpath, len, "%s/%s", dir_path, d->d_name); if (stat (fpath, &st) < 0) { char *err = xstrerror (errno); logit ("Can't stat %s: %s", fpath, err); free (err); free (fpath); closedir (dir); return 0; } if (S_ISDIR(st.st_mode)) { if (!purge_directory (fpath)) { free (fpath); closedir (dir); return 0; } logit ("Removing directory %s...", fpath); if (rmdir (fpath) < 0) { char *err = xstrerror (errno); logit ("Can't remove %s: %s", fpath, err); free (err); free (fpath); closedir (dir); return 0; } } else { logit ("Removing file %s...", fpath); if (unlink (fpath) < 0) { char *err = xstrerror (errno); logit ("Can't remove %s: %s", fpath, err); free (err); free (fpath); closedir (dir); return 0; } } free (fpath); } closedir (dir); return 1; } #endif /* Create a MOC/db version string. * * @param buf Output buffer (at least VERSION_TAG_MAX chars long) */ #ifdef HAVE_DB_H static const char *create_version_tag (char *buf) { int db_major; int db_minor; db_version (&db_major, &db_minor, NULL); #ifdef PACKAGE_REVISION snprintf (buf, VERSION_TAG_MAX, "%d %d %d r%s", CACHE_DB_FORMAT_VERSION, db_major, db_minor, PACKAGE_REVISION); #else snprintf (buf, VERSION_TAG_MAX, "%d %d %d", CACHE_DB_FORMAT_VERSION, db_major, db_minor); #endif return buf; } #endif /* Check version of the cache directory. If it was created * using format not handled by this version of MOC, return 0. */ #ifdef HAVE_DB_H static int cache_version_matches (const char *cache_dir) { char *fname = NULL; char disk_version_tag[VERSION_TAG_MAX]; ssize_t rres; FILE *f; int compare_result = 0; fname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1); sprintf (fname, "%s/%s", cache_dir, MOC_VERSION_TAG); f = fopen (fname, "r"); if (!f) { logit ("No %s in cache directory", MOC_VERSION_TAG); free (fname); return 0; } rres = fread (disk_version_tag, 1, sizeof (disk_version_tag) - 1, f); if (rres == sizeof (disk_version_tag) - 1) { logit ("On-disk version tag too long"); } else { char *ptr, cur_version_tag[VERSION_TAG_MAX]; disk_version_tag[rres] = '\0'; ptr = strrchr (disk_version_tag, '\n'); if (ptr) *ptr = '\0'; ptr = strrchr (disk_version_tag, ' '); if (ptr && ptr[1] == 'r') *ptr = '\0'; create_version_tag (cur_version_tag); ptr = strrchr (cur_version_tag, '\n'); if (ptr) *ptr = '\0'; ptr = strrchr (cur_version_tag, ' '); if (ptr && ptr[1] == 'r') *ptr = '\0'; compare_result = !strcmp (disk_version_tag, cur_version_tag); } fclose (f); free (fname); return compare_result; } #endif #ifdef HAVE_DB_H static void write_cache_version (const char *cache_dir) { char cur_version_tag[VERSION_TAG_MAX]; char *fname = NULL; FILE *f; int rc; fname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1); sprintf (fname, "%s/%s", cache_dir, MOC_VERSION_TAG); f = fopen (fname, "w"); if (!f) { log_errno ("Error opening cache", errno); free (fname); return; } create_version_tag (cur_version_tag); rc = fwrite (cur_version_tag, strlen (cur_version_tag), 1, f); if (rc != 1) logit ("Error writing cache version tag: %d", rc); free (fname); fclose (f); } #endif /* Make sure that the cache directory exists and clear it if necessary. */ #ifdef HAVE_DB_H static int prepare_cache_dir (const char *cache_dir) { if (mkdir (cache_dir, 0700) == 0) { write_cache_version (cache_dir); return 1; } if (errno != EEXIST) { error_errno ("Failed to create directory for tags cache", errno); return 0; } if (!cache_version_matches (cache_dir)) { logit ("Tags cache directory is the wrong version, purging...."); if (!purge_directory (cache_dir)) return 0; write_cache_version (cache_dir); } return 1; } #endif void tags_cache_load (struct tags_cache *c DB_ONLY, const char *cache_dir DB_ONLY) { assert (c != NULL); assert (cache_dir != NULL); #ifdef HAVE_DB_H int ret; if (!c->max_items) return; if (!prepare_cache_dir (cache_dir)) { error ("Can't prepare cache directory!"); goto err; } ret = db_env_create (&c->db_env, 0); if (ret) { error_errno ("Can't create DB environment", ret); goto err; } #ifndef NDEBUG c->db_env->set_errcall (c->db_env, db_err_cb); c->db_env->set_msgcall (c->db_env, db_msg_cb); ret = c->db_env->set_paniccall (c->db_env, db_panic_cb); if (ret) logit ("Could not set DB panic callback"); #endif ret = c->db_env->open (c->db_env, cache_dir, DB_CREATE | DB_PRIVATE | DB_INIT_MPOOL | DB_THREAD | DB_INIT_LOCK, 0); if (ret) { error ("Can't open DB environment (%s): %s", cache_dir, db_strerror (ret)); goto err; } ret = c->db_env->lock_id (c->db_env, &c->locker); if (ret) { error_errno ("Failed to get DB locker", ret); goto err; } ret = db_create (&c->db, c->db_env, 0); if (ret) { error_errno ("Failed to create cache db", ret); goto err; } #ifndef NDEBUG c->db->set_errcall (c->db, db_err_cb); c->db->set_msgcall (c->db, db_msg_cb); ret = c->db->set_paniccall (c->db, db_panic_cb); if (ret) logit ("Could not set DB panic callback"); #endif ret = c->db->open (c->db, NULL, TAGS_DB, NULL, DB_BTREE, DB_CREATE | DB_THREAD, 0); if (ret) { error_errno ("Failed to open (or create) tags cache db", ret); goto err; } return; err: if (c->db) { #ifndef NDEBUG c->db->set_errcall (c->db, NULL); c->db->set_msgcall (c->db, NULL); c->db->set_paniccall (c->db, NULL); #endif c->db->close (c->db, 0); c->db = NULL; } if (c->db_env) { #ifndef NDEBUG c->db_env->set_errcall (c->db_env, NULL); c->db_env->set_msgcall (c->db_env, NULL); c->db_env->set_paniccall (c->db_env, NULL); #endif c->db_env->close (c->db_env, 0); c->db_env = NULL; } c->max_items = 0; error ("Failed to initialise tags cache: caching disabled"); #endif } /* Immediately read tags for a file bypassing the request queue. */ struct file_tags *tags_cache_get_immediate (struct tags_cache *c, const char *file, int tags_sel) { struct file_tags *tags; assert (c != NULL); assert (file != NULL); debug ("Immediate tags read for %s", file); if (!is_url (file)) tags = tags_cache_read_add (c, file, tags_sel, -1); else tags = tags_new (); return tags; } moc-2.6.0~svn-r3005/ChangeLog0000664000076400000500000000002310123261013014661 0ustar riesebiesrcSee the NEWS file. moc-2.6.0~svn-r3005/softmixer.h0000664000076400000500000000161612236532110015317 0ustar riesebiesrc#ifndef SOFTMIXER_H #define SOFTMIXER_H #ifdef __cplusplus extern "C" { #endif #define SOFTMIXER_MIN 0 /* Allow amplification, might result in clipping... */ #define SOFTMIXER_MAX 200 #define SOFTMIXER_NAME "Soft" #define SOFTMIXER_NAME_OFF "S.Off" #define SOFTMIXER_CFG_ACTIVE "Active:" #define SOFTMIXER_CFG_AMP "Amplification:" #define SOFTMIXER_CFG_VALUE "Value:" #define SOFTMIXER_CFG_MONO "Mono:" #define SOFTMIXER_SAVE_OPTION "Softmixer_SaveState" #define SOFTMIXER_SAVE_FILE "softmixer" char *softmixer_name(); void softmixer_init(); void softmixer_shutdown(); int softmixer_get_value(); void softmixer_set_value(const int val); int softmixer_is_active(); void softmixer_set_active(int act); int softmixer_is_mono(); void softmixer_set_mono(int mono); void softmixer_process_buffer(char *buf, const size_t size, const struct sound_params *sound_params); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/Doxyfile0000664000076400000500000001114713012723425014640 0ustar riesebiesrcPROJECT_NAME = MOC PROJECT_NUMBER = 2.6-alpha3 OUTPUT_DIRECTORY = technical_docs CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English USE_WINDOWS_ENCODING = NO BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES MULTILINE_CPP_IS_BRIEF = NO DETAILS_AT_TOP = NO INHERIT_DOCS = YES DISTRIBUTE_GROUP_DOC = YES TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO SUBGROUPING = YES EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO FILE_VERSION_FILTER = QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = INPUT = FILE_PATTERNS = *.c *.h *.doxy RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXAMPLE_PATH = EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = NO COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 GENERATE_LATEX = YES LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = MAX_DOT_GRAPH_WIDTH = 1024 MAX_DOT_GRAPH_HEIGHT = 1024 MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES SEARCHENGINE = NO moc-2.6.0~svn-r3005/tags_cache.h0000664000076400000500000000162712713507754015402 0ustar riesebiesrc#ifndef TAGS_CACHE_H #define TAGS_CACHE_H #ifdef __cplusplus extern "C" { #endif struct file_tags; struct tags_cache; /* Administrative functions: */ struct tags_cache *tags_cache_new (size_t max_size); void tags_cache_free (struct tags_cache *c); /* Request queue manipulation functions: */ void tags_cache_clear_queue (struct tags_cache *c, int client_id); void tags_cache_clear_up_to (struct tags_cache *c, const char *file, int client_id); /* Cache DB manipulation functions: */ void tags_cache_load (struct tags_cache *c, const char *cache_dir); void tags_cache_add_request (struct tags_cache *c, const char *file, int tags_sel, int client_id); struct file_tags *tags_cache_get_immediate (struct tags_cache *c, const char *file, int tags_sel); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/INSTALL0000664000076400000500000003633212202530312014155 0ustar riesebiesrcInstallation Instructions ************************* Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Basic Installation ================== Briefly, the shell commands `./configure; make; make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. Some packages provide this `INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type `make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the `make install' phase executed with root privileges. 5. Optionally, type `make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior `make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type `make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide `make distcheck', which can by used by developers to test that all other targets like `make install' and `make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. This is known as a "VPATH" build. With a non-GNU `make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple `-arch' options to the compiler but only a single `-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the `lipo' tool if you have problems. Installation Names ================== By default, `make install' installs the package's commands under `/usr/local/bin', include files under `/usr/local/include', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option `--exec-prefix=PREFIX' to `configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of `${prefix}', so that specifying just `--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to `configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the `make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, `make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of `${prefix}'. Any directories that were specified during `configure', but not in terms of `${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the `DESTDIR' variable. For example, `make install DESTDIR=/alternate/directory' will prepend `/alternate/directory' before all installation names. The approach of `DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of `${prefix}' at `configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of `make' will be. For these packages, running `./configure --enable-silent-rules' sets the default to minimal output, which can be overridden with `make V=1'; while running `./configure --disable-silent-rules' sets the default to verbose, which can be overridden with `make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put `/usr/ucb' early in your `PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in `/usr/bin'. So, if you need `/usr/ucb' in your `PATH', put it _after_ `/usr/bin'. On Haiku, software installed for all users goes in `/boot/common', not `/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option `--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to an Autoconf bug. Until the bug is fixed you can use this workaround: CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of all of the options to `configure', and exit. `--help=short' `--help=recursive' Print a summary of the options unique to this package's `configure', and exit. The `short' variant lists options used only in the top level, while the `recursive' variant lists options also present in any nested packages. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. `--no-create' `-n' Run the configure checks, but stop before creating any output files. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. moc-2.6.0~svn-r3005/themes.h0000664000076400000500000000206712374730013014572 0ustar riesebiesrc#ifndef THEMES_H #define THEMES_H #ifdef __cplusplus extern "C" { #endif enum color_index { CLR_BACKGROUND, CLR_FRAME, CLR_WIN_TITLE, CLR_MENU_ITEM_DIR, CLR_MENU_ITEM_DIR_SELECTED, CLR_MENU_ITEM_PLAYLIST, CLR_MENU_ITEM_PLAYLIST_SELECTED, CLR_MENU_ITEM_FILE, CLR_MENU_ITEM_FILE_SELECTED, CLR_MENU_ITEM_FILE_MARKED, CLR_MENU_ITEM_FILE_MARKED_SELECTED, CLR_MENU_ITEM_INFO, CLR_MENU_ITEM_INFO_SELECTED, CLR_MENU_ITEM_INFO_MARKED, CLR_MENU_ITEM_INFO_MARKED_SELECTED, CLR_STATUS, CLR_TITLE, CLR_STATE, CLR_TIME_CURRENT, CLR_TIME_LEFT, CLR_TIME_TOTAL, CLR_TIME_TOTAL_FRAMES, CLR_SOUND_PARAMS, CLR_LEGEND, CLR_INFO_DISABLED, CLR_INFO_ENABLED, CLR_MIXER_BAR_EMPTY, CLR_MIXER_BAR_FILL, CLR_TIME_BAR_EMPTY, CLR_TIME_BAR_FILL, CLR_ENTRY, CLR_ENTRY_TITLE, CLR_ERROR, CLR_MESSAGE, CLR_PLIST_TIME, CLR_LAST, /* Fake element to get number of colors */ CLR_WRONG }; void theme_init (bool has_xterm); int get_color (const enum color_index); void themes_switch_theme (const char *file); const char *get_current_theme (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/Makefile.am0000664000076400000500000000431012713507713015167 0ustar riesebiesrcACLOCAL_AMFLAGS = -I m4 SUBDIRS = themes decoder_plugins AM_CPPFLAGS = -DSYSTEM_THEMES_DIR=\"$(pkgdatadir)/themes\" \ -DPLUGIN_DIR=\"$(plugindir)/$(DECODER_PLUGIN_DIR)\" bin_PROGRAMS = mocp mocp_SOURCES = log.c \ log.h \ protocol.h \ protocol.c \ server.c \ server.h \ main.c \ common.c \ common.h \ compiler.h \ playlist.c \ playlist.h \ fifo_buf.c \ fifo_buf.h \ out_buf.c \ out_buf.h \ audio.c \ audio.h \ decoder.c \ decoder.h \ interface.c \ interface.h \ interface_elements.c \ interface_elements.h \ menu.c \ menu.h \ files.c \ files.h \ options.c \ options.h \ player.c \ player.h \ playlist_file.c \ playlist_file.h \ themes.c \ themes.h \ keys.c \ keys.h \ io.c \ io.h \ compat.c \ compat.h \ audio_conversion.c \ audio_conversion.h \ rbtree.c \ rbtree.h \ tags_cache.c \ tags_cache.h \ utf8.c \ utf8.h \ rcc.c \ rcc.h \ softmixer.c \ softmixer.h \ lyrics.h \ lyrics.c \ lists.h \ lists.c \ equalizer.h \ equalizer.c EXTRA_mocp_SOURCES = \ md5.c \ md5.h \ null_out.c \ null_out.h \ sndio_out.c \ sndio_out.h \ oss.c \ oss.h \ alsa.c \ alsa.h \ io_curl.c \ io_curl.h \ jack.c \ jack.h man_MANS = mocp.1 mocp_LDADD = @EXTRA_OBJS@ -lltdl -lm mocp_DEPENDENCIES = @EXTRA_OBJS@ mocp_LDFLAGS = @EXTRA_LIBS@ $(RCC_LIBS) -export-dynamic EXTRA_DIST = README_equalizer mocp.1 THANKS keymap.example Doxyfile \ doxy_pages/decoder_api.doxy doxy_pages/main_page.doxy \ doxy_pages/sound_output_driver_api.doxy EXTRA_DIST += @EXTRA_DISTS@ EXTRA_DIST += tools/README tools/md5check.sh tools/maketests.sh noinst_DATA = tools/README noinst_SCRIPTS = tools/md5check.sh tools/maketests.sh doc_DATA = config.example THANKS README README_equalizer keymap.example moc-2.6.0~svn-r3005/themes/0002775000076400000500000000000013710016723014416 5ustar riesebiesrcmoc-2.6.0~svn-r3005/themes/Makefile.am0000664000076400000500000000042210355046306016450 0ustar riesebiesrcthemesdir = $(pkgdatadir)/themes themes_DATA = transparent-background \ example_theme \ nightly_theme \ green_theme \ yellow_red_theme \ black_theme \ moca_theme \ red_theme \ darkdot_theme EXTRA_DIST = $(themes_DATA) moc-2.6.0~svn-r3005/themes/black_theme0000664000076400000500000000210410247007464016576 0ustar riesebiesrc# Black theme by Arn background = white black frame = white black window_title = white black directory = white black selected_directory = white black reverse playlist = white black selected_playlist = white black reverse file = white black selected_file = white black reverse marked_file = white black bold marked_selected_file = white black bold,reverse info = white black underline status = white black title = white black bold state = white black current_time = white black bold time_left = white black bold total_time = white black bold time_total_frames = white black sound_parameters = white black bold legend = white black disabled = white black enabled = white black bold empty_mixer_bar = white black filled_mixer_bar = white black reverse empty_time_bar = white black filled_time_bar = white black reverse entry = white black entry_title = white black error = white black bold,underline message = white black plist_time = white black moc-2.6.0~svn-r3005/themes/darkdot_theme0000664000076400000500000000205110355046306017151 0ustar riesebiesrc# Theme to match the 'darkdot' vim theme, by David Lazar (david_bv|at|yahoo|com) background = default default frame = white default window_title = white default directory = blue default bold selected_directory = black cyan playlist = white default bold selected_playlist = black cyan file = white default selected_file = black cyan marked_file = white default bold marked_selected_file = white cyan bold info = white default status = white default title = white default bold state = white default bold current_time = white default bold time_left = black default bold total_time = white default time_total_frames = white default sound_parameters = white default bold legend = white default disabled = black default bold enabled = white default bold empty_mixer_bar = white default filled_mixer_bar = black white empty_time_bar = white default filled_time_bar = black white entry = white default entry_title = white default error = white red bold message = white default plist_time = white default moc-2.6.0~svn-r3005/themes/example_theme0000664000076400000500000001161411243250477017164 0ustar riesebiesrc# Example color theme for MOC. # You can use a theme by copying it to ~/.moc/themes directory and using # Theme config option or -T command line option. # # Fill free to make your own themes and send me them. It will be included in # official MOC releases or on the MOC web site. # # The format of this file is: # Lines beginning with # are comments. # Blank lines are ignored. # Every other line is expected to be in format: # # ELEMENT = FOREGROUND_COLOR BACKGROUND_COLOR [ATTRIBUTE[,ATTRIBUTE,..]] # # or # # colordef COLOR = RED GREEN BLUE # # Where names are case insensitive. # # ELEMENT is an element of MOC interface. This can be: # background - default background for regions when nothing is displayed # frame - frames for windows # window_title - the title of the window (eg name of the current # directory) # directory - a directory in the menu # selected_directory - a directory that is selected using arrows # playlist - playlist file # selected_playlist - see selected directory # file - an ordinary file in the menu (mp3, ogg, ...) # selected_file - see selected directory # marked_file - a file that is currently being played # marked_selected_file - a file that is currently being played and is also # selected using arrows # info - information shown at the right side of files # selected_info - see selected directory # marked_info - a file (its time) that is currently being played # marked_selected_info - a file (its time) that is currently being played # and is also selected using arrows # status - the status line with a message # title - the title of the file that is currently being played # state - the state: play, stop, or paused (>, [], ||) # current_time - current time of playing # time_left - the time left to the end of playing the current file # total_time - the length of the currently played file # time_total_frames - the brackets outside the total time of a file ([10:13]) # sound_parameters - the frequency and bitrate numbers # legend - "KHz" and "Kbps" # disabled - disabled element ([STEREO]) # enabled - enabled element # empty_mixer_bar - "empty" part of the volume bar # filled_mixer_bar - "filled" part of the volume bar # empty_time_bar - "empty" part of the time bar # filled_time_bar - "filled" part of the time bar # entry - place wher user can type a search query or a file name # entry_title - the title of an entry # error - error message # message - information message # plist_time - total time of displayed items # # FOREGOUND_COLOR and BACKGROUND_COLOR can have one of the following values: # black, red, green, yellow, blue, magenta, cyan, white, default (can be # transparent), grey (not standard, but works) # # Optional ATTRIBUTE parameters can be (from ncurses manual): # normal - default (no highlight) # standout - best highlighting mode of the terminal # underline - underlining # reverse - reverse video # blink - blinking # dim - half bright # bold - extra bright or bold # protect - protected mode # # You can specify a list of attributes separated by commas: attr1,attr2,attr3. # Don't use spaces anywhere in such a list. # # With colordef you can change the definition of a color. It works only if # your terminal supports it, if not those lines will be silently ignored. # COLOR must be a valid color name and the RED GREEN and BLUE are numbers # from 0 to 1000. Example: # # colordef red = 1000 0 0 # # HINT: you have only 8 colors, but combined with attributes bold and/or # reversed you actually get more colors. # # If you don't specify some elements, the default values will be used. # # Here follows the default configuration: background = white blue frame = white blue window_title = white blue directory = white blue bold selected_directory = white black bold playlist = white blue bold selected_playlist = white black bold file = white blue selected_file = white black marked_file = green blue bold marked_selected_file = green black bold info = blue blue bold selected_info = blue black bold marked_info = blue blue bold marked_selected_info = blue black bold status = white blue title = white blue bold state = white blue bold current_time = white blue bold time_left = white blue bold total_time = white blue bold time_total_frames = white blue sound_parameters = white blue bold legend = white blue disabled = blue blue bold enabled = white blue bold empty_mixer_bar = white blue filled_mixer_bar = black cyan empty_time_bar = white blue filled_time_bar = black cyan entry = white blue entry_title = black cyan error = red blue bold message = green blue bold plist_time = white blue moc-2.6.0~svn-r3005/themes/green_theme0000664000076400000500000000177311572020776016640 0ustar riesebiesrc# green theme by Jacek Lehmann # best viewed on shaded or black terminal background = default default frame = black default window_title = green default directory = red default selected_directory = yellow default playlist = blue default selected_playlist = magenta default file = green default selected_file = cyan default marked_file = green default bold marked_selected_file = cyan default bold info = cyan default status = magenta default title = green default state = magenta default current_time = magenta default time_left = cyan default total_time = cyan default time_total_frames = magenta default sound_parameters = cyan default legend = magenta default disabled = black default enabled = yellow default empty_mixer_bar = green default filled_mixer_bar = black green empty_time_bar = green default filled_time_bar = black green entry = yellow default entry_title = red default error = red default message = yellow default plist_time = magenta default moc-2.6.0~svn-r3005/themes/moca_theme0000664000076400000500000000201610302705532016434 0ustar riesebiesrc# # Theme: moca # Author: Nicola Vitale # background = white black frame = white black window_title = yellow black bold directory = white black selected_directory = white black bold playlist = white black selected_playlist = cyan black bold file = white black selected_file = yellow red bold marked_file = cyan black blink,bold marked_selected_file = cyan red blink,bold info = magenta black bold status = yellow black bold title = cyan black bold state = red black bold current_time = green black bold time_left = magenta black bold total_time = red black bold time_total_frames = red black bold sound_parameters = white black bold legend = white black disabled = white black bold enabled = blue black bold empty_mixer_bar = cyan blue filled_mixer_bar = blue cyan empty_time_bar = green magenta filled_time_bar = magenta green entry = white black entry_title = magenta black bold error = red black bold message = green black bold plist_time = red black bold moc-2.6.0~svn-r3005/themes/nightly_theme0000664000076400000500000000264411572020776017214 0ustar riesebiesrc# Author: Wim Speekenbrink background = blue black frame = blue black bold window_title = blue black bold directory = blue black bold selected_directory = black magenta playlist = blue black bold selected_playlist = black magenta file = blue black bold selected_file = black magenta marked_file = green black bold marked_selected_file = green magenta bold info = green black bold status = blue black bold title = green black bold state = blue black bold current_time = magenta black bold time_left = magenta black bold total_time = magenta black bold time_total_frames = blue black bold sound_parameters = magenta black bold legend = blue black bold disabled = black black enabled = blue black bold empty_mixer_bar = blue black bold filled_mixer_bar = black magenta empty_time_bar = blue black bold filled_time_bar = black magenta entry = green black bold entry_title = green black bold error = red black bold message = green black bold plist_time = green black bold moc-2.6.0~svn-r3005/themes/red_theme0000664000076400000500000000173711572020776016312 0ustar riesebiesrc# rednblack theme by yyz # works good on a light term #property fg bg fx background = default default frame = black default window_title = black default directory = red default selected_directory = black default bold playlist = blue default selected_playlist = black default bold file = red default selected_file = black default bold marked_file = red default underline marked_selected_file = black default bold info = red default status = black default title = red default state = black default current_time = black default time_left = black default total_time = black default time_total_frames = black default sound_parameters = black default legend = black default disabled = white default enabled = black default empty_mixer_bar = black default filled_mixer_bar = black red empty_time_bar = black default filled_time_bar = black red entry = black default entry_title = red default error = red default message = black default plist_time = black default moc-2.6.0~svn-r3005/themes/transparent-background0000664000076400000500000000205111322351762021015 0ustar riesebiesrc# Transparent background theme by Marcin MichaÅ‚owski background = default default frame = white default window_title = white default directory = white default bold selected_directory = white blue bold playlist = white default bold selected_playlist = white blue bold file = white default selected_file = white blue marked_file = green default bold marked_selected_file = green blue bold info = blue default bold status = white default title = white default bold state = white default bold current_time = white default bold time_left = white default bold total_time = white default bold time_total_frames = white default sound_parameters = white default bold legend = white default disabled = blue default bold enabled = white default bold empty_mixer_bar = white default filled_mixer_bar = black cyan empty_time_bar = white default filled_time_bar = black cyan entry = white default entry_title = black cyan error = red default bold message = green default bold plist_time = white default bold moc-2.6.0~svn-r3005/themes/yellow_red_theme0000664000076400000500000000216011572020776017674 0ustar riesebiesrc# Yellow/Red theme - mostly Yellow. By Morten Grunnet Buhl # Doesn't work on terminals when underline is not supported (like Linux console) background = red default frame = red default window_title = yellow default underline,dim directory = yellow default selected_directory = yellow default underline playlist = yellow default selected_playlist = yellow default file = yellow default selected_file = yellow default underline marked_file = yellow default bold marked_selected_file = red default bold,underline info = yellow default status = yellow default title = yellow default state = yellow default current_time = yellow default time_left = yellow default total_time = yellow default time_total_frames = yellow default sound_parameters = yellow default legend = yellow default disabled = red default dim enabled = yellow default bold empty_mixer_bar = yellow red filled_mixer_bar = red yellow empty_time_bar = yellow default filled_time_bar = default yellow entry = yellow default entry_title = yellow default bold error = cyan default message = yellow default plist_time = yellow default moc-2.6.0~svn-r3005/NEWS0000664000076400000500000011215313012723425013630 0ustar riesebiesrc [Note that all relevant maintenance applied to the stable version has also been applied to the development version. You should review the development version changes in conjunction with those of the current stable version maintenance releases.] 2.6 - "Usability" [alpha3: 2016-11-16] * Autotools and packaging changes: - Dropped version micro number for development versions - Upgraded autoconf-archive macros to 2014-02-28 release - Replaced custom shell code with Autoconf archive macros * Changed build behaviours: - 'make dist': now defaults to XZ compression - curl-config: replaced by pkg-config - New GCC-5 and 6 warnings addressed - Removed checks for POSIX.1 and C99 mandated headers and functions * Changed minimum support and release requirements: - POSIX.1: introduced minimum requirement of IEEE 1003.1-2001 - C Compiler: introduced minimum requirement of ISO 9899:1999 - FFmpeg/LibAV: raised minimum requirement to release 1.0/10.0 - FLAC: raised minimum requirement to release 1.1.3 - Berkeley DB: raised minimum requirement to release 4.1 - libcurl: raised minimum requirement to version 7.15.1 - autoconf: raised minimum requirement to version 2.64 - ALSA: raised minimum requirement to version 1.0.11 * New and changed library requirements: - POPT library: MOC now requires libpopt * Documentation: - Improved wording of some error messages - Added environment variables section to the manpage * Removed functionality: - Transitional code from the previous release - autogen.sh in favour of autoreconf(1) - Deprecated ffmpeg-config support - Warning of changed executable name - Removed "Search Next" interface command * Changed functionality: - Decoupled mono-mixing from softmixer - Made command line toggling arguments case insensitive * Added functionality: - Introduced in-memory circular logging buffer - Introduced MOCP_POPTRC environment variable - Introduced MOCP_OPTS environment variable - Add 24-bit support to the OSS sound driver (Vladimir Krylov) * New and changed command line options: - echo-args: Show POPT-interpreted command line arguments * New and changed audio decoders: - All sndfile-supported extensions are now available - Added .mat[45] extensions as aliases for .mat in sndfile decoder - Added .ircam extension as alias for .sf in sndfile decoder - Added reading from Internet streams to FFmpeg decoder (Jenny Wong) * Changes to supported formats and codecs: - All formats provided by Sndfile are now supported - DSD: now supported via FFmpeg/LibAV (.dff and .dsf files) - TTA: now supported via FFmpeg/LibAV - VQF: now supported via FFmpeg/LibAV * Miscellaneous - Improved logging from ALSA sound driver and library - Fixed displayed ALSA volume percentage drift 2.5.2 [2016-11-16] * New configuration file options: - ALSAStutterDefeat: avoid the ALSA bug which causes stuttering * Significant bug fixes: - Fixed error during configure on busybox systems - Fixed calculation of average bitrate in FLAC decoder - Fixed unintentional disabling of MMAP support - Fixed build failure on split ncurses/tinfo systems - Fixed unreaped children when running in foreground mode - Fixed freeze on Nokia devices if audios play to completion - Circumvented ALSA stutter bug - Added missing 'Precache' option to example config file * Miscellaneous: - Improved the accuracy of the average bitrate for FLAC - Mitigated out-of-file seeking for several formats - Warn of TagLib version requirement rising to 1.5 - Warn of Musepack library changing to libmpc (from libmpcdec) 2.5.1 [2016-02-24] * Significant bug fixes: - Corrected the setting of linked libraries for Berkeley DB - Fixed ALSA volume setting problem (Tomasz Golinski) - Fixed clearing of stream status message on error - Resolved FFmpeg/LibAV's downmixing API issues (Andreas Cadhalpun) - Removed duplicate logging of fatal error messages * Miscellaneous: - Adapted to FFmpeg 3.0 API - Warn of FFmpeg/LibAV version requirement rising to 1.0/10.0 - Warn of pending POSIX.1-2001 compliance requirement - Fixed several warnings and errors on OpenBSD (Brent Cook) - Fixed various (and potential) segfaults (Daniel T. Borelli, Hendrik Iben, Rastislav Barlik) - Fixed various resource leakages - Silenced various compiler warnings - Added instructions on building with autoreconf 2.5.0 - "Consolidation" [2014-08-13] * Autotools and packaging changes: - Upgraded autoconf version requirement to 2.60 - Added '--with-alsa' to suppress ALSA sound driver inclusion - Added '--without-oss' to suppress OSS sound driver inclusion - Added '--disable-cache' to remove tags cache support - Added specific GDB support to '--enable-debug' - Refactor decoder plug-ins' autoconf scripts into source directories - Ensure that all decoders get into the source distribution tarball - Removed SID decoder's dependance on .la-file presence - Removed distribution-specific .spec file - Minor reformatting of the configure summary - Added warnings for various deprecations and potential problems - Added warnings for unmet future package requirements - Updated GNU boilerplate text * Improved support for embedded systems: - Refined FFmpeg decoder configuration for use with cross-compilation - Provided use of Tremor with the Vorbis decoder - Improve portability to non-GNU library platforms - Added --with-libiconv-prefix configure option * Audio driver changes: - Allow use of the OSSv4 per-application mixer API - Provided SNDIO support for OpenBSD systems (Alexander Polakov) * New and changed audio decoders: - New TiMidity decoder for MIDI (Hendrik Iben) - Migrate AAC decoder to later FAAD2 API (Max Klinger) - Added AAC+ (HE-AAC) support to AAC decoder - New SidPlay2 decoder for SID (Hendrik Iben) - New Modplug decoder (Hendrik Iben) - New WavPack decoder (Alexandrov Sergey) - Renamed SndFile plugin for consistancy - Removed M4A format support from AAC decoder plugin - Removed WAV format support from ModPlug decoder plugin - Detect huge files in certain formats which SndFile cannot play - Improved (drastically in some cases) the accuracy of AAC durations * Overhauled FFmpeg/LibAV decoder: - Resolved FFmpeg API deprecations - Provided LibAV compatibility - Provided proper stereo downmixing - Provided locking support for non-thread-safe library functions - Provided better FFmpeg or LibAV discrimination - Provided better audio duration reliability determination - Increased number of decodable formats (including Xiph Opus) - Added decoding of audio from video formats - Added logging of FFmpeg/LibAV messages - Added seeking in most (but not all) formats - Added handling for "planar" codecs - Excluded experimental codecs from decoding - Fixed misreporting of tags, duration and bitrates - Fixed memory and file descriptor leakages - Fixed severe distortion on 8-bit samples - Fixed loop playing FLAC files - Fixed many FFmpeg/LibAV API breakages - Fixed many miscellaneous bugs - Detect over-length (and therefore broken) WAV files - Fixed log formatting when FFmpeg messages contain newline characters * Audio reproduction changes: - Improved support for 8-bit sample size - Added software mixer (Hendrik Iben) - Added parametric equalizer (Hendrik Iben) - Fixed many bugs which produced distorted sound - Fixed bugs in 24-bit sample handling (Tomasz Golinski) * General configuration file changes: - Reconciled and regularised example config and keymap files - Introduced lists and function-like syntax - Introduced variable substitution - Introduced symbol and boolean option types - Improved security of the configuration file - Automatic clearing of an overridden default key binding - Made processing of keymap file consistant with that of config file * Changed configuration file options: - Layout# options moved to a list and function-like syntax - SoundDriver option moved to a list syntax - Renamed OSSMixerChannel to OSSMixerChannel1 - Renamed ALSAMixer to ALSAMixer1 - QueueNextSongReturn moved to yes/no values - TagsCacheSize set to zero now disables tag caching * New configuration file options: - OnSongChange: run an external command (Jack Miller) - RepeatSongChange: govern the running of the OnSongChange command - OnStop: run an external command on stopping - EnforceTagsEncoding: substitutes ID3v1TagsEncoding for ISO-8859-1 encoding in ID3v2 tags (Aleks Sherikov) - FileNamesIconv: converts from local to UTF8 encoding for file names (Aleks Sherikov) - NonUTFXterm: converts UTF8 to local encoding for X-Term titles (Aleks Sherikov) - AutoLoadLyrics: says whether MOC should look for lyrics files - PreferredDecoders: allow finer control over decoder selection - XTerms: externalises terminals regarded as X-Terms - UseMIMEMagic: says whether to identify audio files by using MIME - JackStartServer: autostart JACK the server (Max Klinger) - ShowTimePercent: set the percent played state (Daniel T. Borelli) - Various options for SidPlay2, Modplug and TiMidity support * New command line options: - '-j' to jump to some position in the current track (Nuno Cardoso) - '-O' to override configuration file settings - '-Q' to display user formatted information (Juho Hämäläinen) - '-q' to queue files from command line (Martin Milata) - '-t' and '--on/off' to toggle or set playback options (Jack Miller) - '-v' to set the volume (Jack Miller) * Screen handling changes: - Changed minimum screen height to 7 lines (Tero Marttila) - Added support for GNU screen title (Jonathan Derque) - Restored screen to console mode after reporting fatal errors - Populated playlist panel when loading default playlist file - Removed default playlist autofocus at start - Fixed overlength highlight bar - Fixed screen upset when tags contain control characters - Fixed some screen upsets when tags contain UTF-8 characters (firejox) - Fixed screen upset caused by screen(1) mis-detection * New and updated client interaction features: - 'a' command: also add a directory to the playlist (Filippo Giunchedi) - 'L' command: display music lyrics (Géraud Le Falher) - 'Y' command: prune unreadable files from the playlist (tyranix) - Queued messages for display in the message area - Added play queue (Martin Milata) - Mark a fragment of a file which can be passed to external commands - Clear status message after a stream open failure - Minor help menu clarifications * Improve text entry history and editting: - Recognise ^u (by default) as delete to start of line - Recognise ^k (by default) as delete to end of line - Save and restore entered text prior to history scrolling - Save history entry modifications - Do not save adjacent duplicate entries - Do not save or modify with blank entries - Position cursor at end of restored or history line * Theme changes: - Enabled highlighted playlist numbers and file times (Marc Tschiesche) - Fixed miscoloured frame when switching themes (Alexander Polakov) - Fixed default colour settings - Fixed cursor placement in themes menu (Alex Merenstein) - Fixed ordering of theme files in themes menu - Fixed new attributes application on theme switching (Alex Merenstein) * General code cleaning: - Refactoring, optimisations and cosmetic improvements - Silenced many build warnings and errors - Replaced various deprecated, legacy and platform-specific functions - Improved thread safety * Significant bug fixes: - Fixed stale locks freeze in tags cache database following a crash - Fixed CURL timeout for internet streaming errors (Daniel Stenberg) - Fixed audio distortion on MP3 file having overly long tag values - Fixed false positive stream detection in the MP3 decoder - Fixed Ogg/Vorbis stream detection in the Vorbis decoder - Fixed 'LRINTF error' raised when reconfiguring MOC - Fixed backspace key mishandling - Fixed client interface meta-key handling - Fixed pthread stack overflow segfault on OpenBSD - Fixed segfault when text entry history becomes full - Fixed segfault processing playlists with relative paths - Fixed memory corruptions when toggling tag reading - Fixed assertion when a second client is started - Fixed slow memory leak in client on long-playing streams - Fixed severe distortion on 11025Hz 16-bit mono audios - Fixed freeze at end of playing audio file - Fixed server crash when attempting to play a deleted file - Fixed MIME type detection on HTTP redirection - Fixed crash when mixer value is above 100% - Fixed handling of huge (greater than 2 GiB) files - Fixed sub-second audio truncation on ALSA - Fixed segfault when MIME-detected MP3 file has no "extension" - Fixed segfault when using '--format' without an audio playing - Workaround for streams that have the actual title as tags/comments - Indentified cause of "undefined symbol" build errors - Plugged all known memory and file descriptor leaks - Fixed nonsense duration values returned on some corrupt FLAC files - Fixed 'time >= 0' assertion at change of audio file - Fixed client event notification failure in some circumstances - Fixed client abort on duplicated playlist filenames - Fixed delayed client exit when server invokes long-running scripts (Alex Merenstein) - Fixed occasional server freeze on logging when running scripts - Fixed non-conforming 'User-Agent' HTTP request header * Miscellaneous: - Updated and reformatted mocp manpage and equalizer README file - Made many warning and error reports more informative and consistant - Better logging of more problem determination information - More informative version information display - Introduced MD5-based decoder verification tools - Improved compatibility with 64-bit systems - Improved compatibility with big-endian systems - Moved tags cache to a Berkeley DB - Integration of down-stream distribution patches - Fixed frames to duration calculations in ALSA - Fixed some mutex management bugs - Fixed many bugs which crashed MOC 2.4.4 [2009-01-04] * Fix crash when saving a playlist with URLs. * Fix hang in case of symlink loop (like symlink to parent directory) when adding files recursively to the playlist. * Fix bad memory access when using FILL parameter in layouts that caused "FATAL_ERROR: Layout1 is malformed". * Fix compilation with newer ffmpeg. (Alexis Ballier) * Increase maximum file title (made from tags) length to 512. Helps on wide terminals. * Fix displaying URLs on the playlist when it ends with a slash. * Fix compilation of flac plugin by detecting libflac using pkg-config. * Fix for multichannel playback. (Maarten van Es) * Fix handling of invalid track number in tags. A segfault occurred when the track number was at least a value of 2^31. * Support for the new (SVN) libmpcdec API. * Remove old, irrelevant comment about iconv from the configuration file. * Fix configure to display proper information about compiled RCC. * Run libtoolize in autogen.sh to prevent conflicts with installed libtool/libltdl version. 2.4.3 [2007-07-30] * Fix displaying tags from Internet streams, sometimes the name of the station or other less useful text was displayed instead the title. * Fix a problem with opening Internet streams with curl 7.16.x. (Samid Tennakoon) * Fix XTermTitle for urxvt. * Fix delete and backspace keys in entries. * Fix a race (crash) when issuing the next commands one after another very fast. * Fix problems with vmix OSS virtual driver (no supported audio formats). * Fix FollowPlayedFile after CLI restart. * Updated moc.spec (Klaus Ethgen) 2.4.2 [2007-06-10] * Disable 24bit output by default due to reported problems with some sound cards. It can be enabled by setting Allow24bitOutput option to yes. * Fix escape key handling. (Jack Miller) * Fix CTRL-key combinations in entries. (Jack Miller) * Fix a crash when a file is precached and user requests playing a different file. * Disabled using mmap() for reading files in the default configuration due to reported incrased memory usage. * Fix ffmpeg build failure due to LOG_H defined by ffmpeg headers. * Fix reading PLS playlists with more than 9 entries. * Fix configure script: libiconv is required to build moc. * Fix the A command for '..' directory (Debian Bug#416102) * Fix displaying the playlist panel when the width of the terminal is an odd number. 2.4.1 [2006-02-12] * Added Command 'P' and a configuration option PlaylistFullPaths to turn on/off displaying full paths for files in the playlist. * Fixed choosing endianess when playing float samples (musepack or wave and other libsndfile formats). * Fixed a crash when going to '../' with the search entry. * Added MP4 to the list of supported by FFmpeg extensions. * Fixed clearing playlist from the command line (crash on mocp -c -a -p file.mp3). * Fixed handling mixer errors (crash in some situations). * Fix searching with '/' (search began with the current item, not the whole list). * Correct support librcc: tags ID3v1/v1.1 will be recoded in UTF-8 instead of the local encoding. (Alexey Gladkov) * Fixed handling invalid time in tags cache. * Fixed handling error when time information for a file could not be read. * MusicDir and FastDirX are parsed before they are used, this handles not "clean" directories in the config file (like /bin/ - with slash at the end). * When interface is killed by SIGTERM or SIGHUP the playlist is saved and the terminal mode is restored. * Setting volume in jack using an exponential function instead of linear. (x37v.alex) * Draw bottom lines for side menus that don't touch the botom of the main menu. * Reload directory content after running a custom command to see changes made in the filesystem by this command. * Added missing exec_command# keys in the example keymap file. 2.4.0 [2006-02-12] * Layout of the main window can be changed using Layout[123] options. Switching between layouts is done using the 'l' key and now TAB is used to switch between the playlist and the directory menu. For example, you can configure the layout to see both the playlist and a directory content. * Support for WMA, RealAudio, MP4 and AAC file using FFmpeg. * UTF-8 support. * Selecting themes at runtime - T command (this does not change the config file). * Executing external commands, like 'cp %f /mnt/usb_drive' where %f is substituted with the path to the currently selected file. * Tags are cached at the server side and read in a separate thread. The interface is not locked until the tags are read. Size of the cache can be adjusted using TagsCacheSize. The cache is saved at exit and loaded at startup. * Moving items up and down: u and j commands. * Workaround for encoding of ID3v1 tags. New options: UseRCC - to use librcc for ID3v1 reencoding (Initial patch by Alexey Gladkov), ID3v1TagsEncoding - assumed encoding for ID3v1 tags. * Added UseCursorSelection option (default: no) to display cursor on the selected file. This is useful with braille displays. * Added SetXtermTitle option (disable/enable setting xterm title). * Added m4a and aac to the list of extensions supported by ffmpeg. * Pressing n when nothing is played starts playing from the first item on the playlist. * Added FollowPlayedFile option: menu follows the currently played file so that it is scrolled if the file is outside the visible part (default to yes). * Numbering items in the playlist. Can be turned off using PlaylistNumbering. * New themes: moca_theme (Nicola Vitale), red_theme (yyz), and darkdot_theme (David Lazar). * Added a command for adding a URL to the playlist using entry (CTRL-u). * A and --append can add files from playlists. * Interface show the playlist after startup if something from the playlist is played (CanStartInPlaylist option). * Commands: --append, --clear, --play work now as expected even if there is no client running. * Use full paths instead of just file names for displaying on the playlist. * Internet streams can be paused. * Ogg plugin name was changed to vorbis. * Added RPM SPEC file. (Fredrik Rambris) * Redesign of the interface code. This is a big change in the code, but not really visible to the user. It was necessary to maintain and extend the interface in the future. * MOC can now be compiled under OpenBSD. Thanks to Tilo Stritzky for pointing out the issues. * Striping leading and trailing white characters from URLs entered by the user. * Count speex time without scaning the whole file. * -e is an alias for -a. * Display the current time for Internet streams. * MusicDir can be a playlist. * Silent seeking can be configured using SilentSeekTime option. * After adding a file to the playlist, the cursor is moved down. * The help screen's position is kept when it's not displayed. * Some operations like deleting items from the playlist should now be faster. * Fixed a possible deadlock while seeking. This fixes a hang on FreeBSD 6. * Fixed OSS support on some machones (like G3 iBook). 2.3.3 [2006-01-04] * Workaround for backspace key on many terminals (like aterm). * Fixed a memory leak when playing Internet streams. (rixx) * Fixed a crash when an mpc file coudn't be read. * Fixed a memory leak occuring with every played mp3 file. * Fixed a memory leak when seeking in MPC file. * Fixed compilation on OpenBSD. (Tilo Stritzky) * Fixed parsing Icy metadata packages (possible segfault). * Fixed resource leak when reading tags from invalid OGG file. * Check if FileNamesIconv has valid format at startup like TagsIconv. 2.3.2 [2005-09-25] * Optimized 24bit->16bit conversion. Helps playing mp3 on handheld devices without FPU. * Fixed a crash when using the playlist with ReadTags turnedoff. * Fixed detecting taglib 1.4. * Fixed mutex initialization in io objects (crash on some systems - FreeBSD). * Moved the man page to section 1. * Fixed bahaviour when one or both OSS mixer channels are not available. * Sort file names using the current locale settings. (breg) * Fixed -p description. * Fixed a memory leak when adding an item to a full entry's history. * Slightly less CPU usage in the client due to elimination of useless bitrate updates. * Fixed a typo: owerwrite -> overwrite. 2.3.1 [2005-08-02] * Fixed detecting MPEG stream by the content (Shoutcast fix). * Fixed handling ALSA mixer events (crash in some situations). * Fixed handling a mixer channel that has no playback volume. * Fixed crash when the alsa mixer couldn't be opened. * Fixed crash when nothing was decoded (appears sometimes when holding the enter key on an mp3 file). * Fixed a crash which shows up on FreeBSD 5.4 after playing a file. Thanks to Joseph Dunn for help. * Fixed includes for maximum and minimum values of intiger types (fix compilation on some systems). * Fixed the syntax (not a proper C code). Mostly taken from the FreeBSD port. * Show stream URL instead of nothing if the title is not available. * Fixed yellow_red_theme theme. * Fixed a hang after file open error. * Fixed counting time after file open error. * Fixed some gcc4 warnings. 2.3.0 [2005-07-09] * Network streams (shoutcast, icecast, regular HTTP, FTP). You can load an m3u file with a URL or use the 'o' command. * JACK output (by Alex Norman). * Added support for musepack (mpc). * The search command filters out not matching elements from the menu and allows to walk through the items like in the regular menu. * Added support for speex format. * Plugins: to drop dependencies from many exotic libraries, support for file formats was moved to shared libraries. This should help making MOC packages in future when more formats will be added. * Sample rate conversion using libsamplerate and some sound conversions like 16bit -> 24bit etc. With 24bit sound cards 24bit output is used with mp3. * Added support for PLS version 2 playlists. * Added mono to stereo conversion. * MOC now compiles using libtool. * Error messages for precached files are not displayed while other file is being played. * Input buffer in a separated thread is used for reading files (new options: InputBuffer and Prebuffering). * New theme: Yellow/Red (by Morten Grunnet Buhl). * Added -A command line option and ASCIILines config option that disables usage of graphic characters to draw lines. * Added commands to set volume from 10% to 90% in 10% steps, default bindings are ALT-1 to ALT-9. * Added commands to quickly go to a selected directories (by Alex Norman). * Added --next and --previous command line options (by Alex Norman). * Added --info command line option that prints all information about the currently played file. (Based on the code by Michael Banks) * Two mixer channels can be set in the configuration file. They can be switched at run time by pressing x. * Documentation of some parts of the code in Doxygen format. * Colors can be redefined in themes using 'colordef COLOR = R G B' (works for terminal that can change the colors). * Support for 24bit flac files (not tested). * Added SeekTime option: how fast the seeking is. (Kamil Tarkowski) * A list of sound drivers can be used instead of only one driver in the configuration file. The first working driver will be used. * MusicDir can be set to a playlist. * Alsa mixer has always 0-100 range despite the actual device range. * Added --toggle-pause command line option. * Don't block the audio device when paused. Based on a patch by hondza * Treat an option that is set in the config file more than once as an error. * Print descriptions for some errors in the config file. * Better error messages in the Ogg plugin. * Better detecting Ogg vorbis stream by content. * Decreased the time to wait for the pcm to become ready to get samples in alsa. This helps with underruns. * Fixed protect attribute in themes. * Added black_theme (by Arn). * History for "go to a directory" and "enter URL" commands (using arrows). * CTRL-l like CTRL-r refreshes the screen. * Separated iconv() conversion for file names and tags. * Improved performance a bit when operating on big playlists. * G command points to the currently played file - on the playlist if it's there, and selects it in the menu. * When going to a directory using the i command, TAB completes to the matching part of ambiguous directories. * Volume changes made by other programs are detected. * Added --recursively command line option (make a playlist from the content of a directory given at command line). * Show bitrate in the right time - the value corresponds to what you can hear, not the position that the decoder is at. * Added description about creating a decoder plugin. * Added introduction pages to Doxygen documentation. * Documented decoder plugins API. * Added --playit option (play files given on the command line without modifing the playlist). * Added UseRealtimePriority (default no) option: set realtime priority for the output buffer thread. * Titles in saved playlists are not converted by iconv(). * Fixed crash on some systems when a file is precached. * Refuse to run if an ALSA mixer channel is wrong. * Fixed adding items to the playlist when the server's playlist already has added files. * Don't try to use colors on black/white terminals. * Fixed a memory leak in iconv_str() when iconv() is not available. (breg) 2.2.2 [2005-05-16] * Added an option to ignore CRC errors in mp3 files (set to yes by default, like most players do). This fixes playing mp3 files that have bad CRC checksums but are actually good. * Fixed marking the currently played item when a new item on the playlist appears. * Fixed a crash when going back at the first item with Repeat turned on (Debian Bug#307651). * Fixed a crash when the time of a file can't be read. * Fixed handling FLAC files with 8-bit samples. * Added mp2, mp1, and mpga to the list of supported extensions by MAD: this makes possible to play MPEG Layer I and II files. * Fixed a race condition that crashes MOC when switching songs quickly. * Fixed a small memory leak in FLAC. * Ogg Vorbis is not required for compilation (a bug in the configure script). * Use a pipe to wake up another thread from select() instead of a signal. Helps on NetBSD where pthread_kill() doesn't interrupt select(). * Fixes for compiler warnings on 64-bit systems. * Fixed a warning about wprintw() usage on some systems. * Fixed setting compiler flags for vorbis. 2.2.1 [2005-04-16] * Fixed missing titles of files without tags when added to the playlist. * Fixed refreshing the screen after searching. * Fixed recognizing AIFF file extension. * Fixed compiler warnings on 64bit systems. * Fixed detecting ncurses, now also works with curses. * Fixed compilation errors on ioctl() use on some systems. * Support for libossaudio (NetBSD). * Fixed an compiler warning about EV_ERROR on NetBSD. * Better checking for vorbis. * Fixed --without-sndfile configure option. * Fixed a rare deadlock. 2.2.0 [2005-02-26] * Added support for FLAC audio format. * Added support for various file formats like au, aiff, voc and more using libsndfile. This also provides better support for wav files. * Custom keymaps can be used. * Synchronizing the playlist between clients (interface instances). * Going to a directory by typing the path with file-name completion (i command). * Ability to use default (transparent) and grey color. (Marcin MichaÅ‚owski). * Added a theme with transparent background (Marcin MichaÅ‚owski). * Added nightly_theme (Wim Speekenbrink). * Added green_theme (Jacek Lehmann). * Global theme directory. * MOC should now work on big-endian processors. * Added XTermTheme option to use a different theme when running on XTerm. * Fixed the 'next' command when AutoNext option is turned off. * Displaying the total time of files on the playlist and in directories. * Faster reading and saving playlists. * Added --pause and --unpause command line parameters. * Added AlsaMixer and AlsaDevice options. * Added G command: go to a directory when the currently played file is. * Added U command: go to '..'. * Recognize if a file was modified and rereading tags and time if necessary. * Fast, silent seeking ('[' and ']' keys). * The cursor is hidden when it's not needed. * Changed the erroneous name of the time_left_frames to time_total_frames in themes. * Added '/' command working as an alias for g (Kamil Tarkowski). * Added CTRL-n working like CTRL-g (Kamil Tarkowski). * ? key working like h (Kamil Tarkowski). * Playlist time displayed in format 000:00:00 (Kamil Tarkowski). * True shuffle, not a random item from the playlist. * Changing the icon name when changing the xterm title (Jacek Lehmann). * Arrow keys and the delete key works in the entry field. * Detecting other terminals beside xterm (Jacek Lehmann). * Added b (back) command (Kamil Tarkowski). * A bit faster getting files time. * Added SavePlaylist option. * Added a bar showing time of the current file. * Repeat without AutoNext plays a song in loop. * Added (empty|filled)_mixer_bar, (empty|filled)_time_bar to themes. * Fixed setting sound parameters while playing OGG. This caused crashes on big endian machines (Philippe De Muyter). * Fixed crash on some file tags. * Fixed crash when issuing some errors. * Fixed the --help message. * Fixed bad terminal state after exiting the client in rare cases. * Fixed adding directories recursively when ReadTags is turned off. * Fixed going to the root directory with the i command. * Fixed getting time when nothing is played. * iconv() (character set conversion) for file names. * END and HOME keys working in the i command entry (go to a directory). * Synchronizing adding/deleting items from the playlist with the server's playlist. * Faster operations on big playlists. * Fixed saving playlist when more than one client wants to do this. * Fixed recursive adding file to the playlist if some directory can't be read. * Fixed deleting items from the playlist. * Fixed handling a playlist file as command a line argument. * Fixed compilation with --disable-debug. * Fixed --version information. * Fixed sorting when using the A command. * Position of time and format in menu is fixed. * Searching for a theme in the user directory first. * Fixed a memory leak when reading OGG tags. * Few fixes when running MOC with file names or directories as arguments. * Fixed reading track numbers for mp3 files. * Small fixes of ALSA behaviour when underrun occurs. 2.1.4 [2004-12-31] * Fixed the 'next' command when AutoNext is turned off. * Sorting the files before adding them to the playlist when using the A command. * Fixed a memory leak when reading tags of ogg files. * Fixed reading track numbers of mp3 files. * Detect other X terminals beside xterm. * Fixed error messages for mp3 files. * Fixed segfault when reading some tags. 2.1.3 [2004-11-15] * Fixed compilation with header files not from Linux 2.4. 2.1.2 [2004-11-14] * Fixed playing mp3 with UseMmap turned on. * Fixed handling ALSA device open errors. * Proper handling 8, 16, and 24 bit sound, raise error if using anything else. * Fixed a (almost :) deadlock when pressing stop when seeking. 2.1.1 [2004-11-13] * Added missing example_theme. 2.1.0 [2004-11-11] * Multiple clients. You can have moc interface on many consoles/terminals. * Searching the menu (CTRL-g) like M-s in Midnight Commander. * Playlist load/save support (m3u files). * The apperance can be customized using themes. * Displaying file time and format in the menu. * Precaching files and not closing the audio device when playing the next file - no more gaps between two songs. * ALSA support. * Optional character set conversion using iconv(). * MOC remembers the playlist between runs. * Option to hide file names extension. * Use of mmap() can be turned off (usefull on NFS). * Ability to use different config file/directory tan the default. * Pressing CTRL-C interrupts long operations that blocks the interface like reding tags for hundreds of files. * Fixed seeking and counting time in VBR mp3 files. * Fixes for systems where threads are not processes. * Small fixes and cleanups. 2.0.1 [2004-10-21] * Do not allow to add '..' to the playlist. * Fixed seeking when not playing. * Fixed segfault when using f after creating a playlist from the command line. * Fixed invoking MOC with relative path arguments * Fixed displaying file title longer than the window. * Fixed broken menu after resizing terminal when help screen is displayed. 2.0.0 [2004-10-16] * Split into client-server, you can detach the interface and leave the server playing songs in the background (keys: q - background, Q - quit). * Output buffer with seperate thread. * Using OSS directly - better stop and pause. * Support xterm resizing. * Using xterm title. * Autonext option (X key). * Faster reading directory contents. * Displaying time left to the end of the songs. * Fixed moving through symlinked directories. * Renamed executable file to mocp due to conflict with QT meta object compiler. * Configurable mixer channel, OSS output device, and mixer device. * ^r - refresh the screen, r - reread the directory. * H command and ShowHiddenFiles - as the option says. * Do not allow displaying bitrate too fast to read it. * Fixed compilation with pthreads, better configure. * Fixed problems when compiling with gcc 3.3 * Fixed displaying window title longer than the window. * Space key working as pause. * Fixed possible segfault in trenary expressions. * Removed version checker. * Fixed sorting directories ('../' was not always on top). * Fixed escaping characters in config file, no need for '\\\\'. * Stronger checking for parse errors in configuration file. 1.1.0 [2002-12-08] * Support for WAV. * Playlist without reading/writeing to file. * Option in the config file to automaticaly change process priority at startup. Helpful on slow computers. * Recursivaly adding files from comamnd line and from the interface. * Shuffle and repeat. * Nice progress bar for longer operations. * Fixed segfault when using the END key. * Fixed segfault when playing OGG files. * Workaround for kde and gnome terminals. * Now MOC can be compiled on FreeBSD. * Other fixes. 1.0.0 * First release intended to be stable and user-friendly :) * Added man page. * Added version checker (simple command to see if a new version of MOC has been released). * Fixed problem when compiling without ogg. * Added redraw command. * Other small fixes. 0.9.2 * Fixed -s option. * Added 'f' command (switching between short and full file names). * Added README file. * Added support for config file. * Added MusicDir option, 'm' command and '-m' switch. * Added ability to turning off messages about broken streams. * Fixed including dirent.h for various OS. * Fixed and updated configure. 0.9.1 * Added mixer (using OSS). * Added help screen. * Fixed some tiny buffer overflows. * Small fixes in code. 0.9.0 * First stable version. moc-2.6.0~svn-r3005/themes.c0000664000076400000500000003766012673120505014575 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined HAVE_NCURSESW_CURSES_H # include #elif defined HAVE_NCURSESW_H # include #elif defined HAVE_NCURSES_CURSES_H # include #elif defined HAVE_NCURSES_H # include #elif defined HAVE_CURSES_H # include #endif #include #include #include #include #include #include "common.h" #include "interface.h" #include "themes.h" #include "files.h" #include "options.h" /* ncurses extension */ #ifndef COLOR_DEFAULT # define COLOR_DEFAULT -2 #endif /* hidden color? */ #ifndef COLOR_GREY # define COLOR_GREY 10 #endif static char current_theme[PATH_MAX]; static int colors[CLR_LAST]; /* Counter used for making colors (init_pair()) */ static short pair_count = 1; /* Initialize a color item of given index (CLR_*) with colors and * attributes. Do nothing if the item is already initialized. */ static void make_color (const enum color_index index, const short foreground, const short background, const attr_t attr) { assert (pair_count < COLOR_PAIRS); assert (index < CLR_LAST); if (colors[index] == -1) { init_pair (pair_count, foreground, background); colors[index] = COLOR_PAIR (pair_count) | attr; pair_count++; } } static void set_default_colors () { make_color (CLR_BACKGROUND, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_FRAME, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_WIN_TITLE, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_MENU_ITEM_DIR, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_MENU_ITEM_DIR_SELECTED, COLOR_WHITE, COLOR_BLACK, A_BOLD); make_color (CLR_MENU_ITEM_PLAYLIST, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_MENU_ITEM_PLAYLIST_SELECTED, COLOR_WHITE, COLOR_BLACK, A_BOLD); make_color (CLR_MENU_ITEM_FILE, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_MENU_ITEM_FILE_SELECTED, COLOR_WHITE, COLOR_BLACK, A_NORMAL); make_color (CLR_MENU_ITEM_FILE_MARKED, COLOR_GREEN, COLOR_BLUE, A_BOLD); make_color (CLR_MENU_ITEM_FILE_MARKED_SELECTED, COLOR_GREEN, COLOR_BLACK, A_BOLD); make_color (CLR_MENU_ITEM_INFO, COLOR_BLUE, COLOR_BLUE, A_BOLD); make_color (CLR_MENU_ITEM_INFO_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD); make_color (CLR_MENU_ITEM_INFO_MARKED, COLOR_BLUE, COLOR_BLUE, A_BOLD); make_color (CLR_MENU_ITEM_INFO_MARKED_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD); make_color (CLR_STATUS, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_STATE, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_TIME_CURRENT, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_TIME_LEFT, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_TIME_TOTAL_FRAMES, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_TIME_TOTAL, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_SOUND_PARAMS, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_LEGEND, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_INFO_DISABLED, COLOR_BLUE, COLOR_BLUE, A_BOLD); make_color (CLR_INFO_ENABLED, COLOR_WHITE, COLOR_BLUE, A_BOLD); make_color (CLR_MIXER_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_MIXER_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL); make_color (CLR_TIME_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_TIME_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL); make_color (CLR_ENTRY, COLOR_WHITE, COLOR_BLUE, A_NORMAL); make_color (CLR_ENTRY_TITLE, COLOR_BLACK, COLOR_CYAN, A_BOLD); make_color (CLR_ERROR, COLOR_RED, COLOR_BLUE, A_BOLD); make_color (CLR_MESSAGE, COLOR_GREEN, COLOR_BLUE, A_BOLD); make_color (CLR_PLIST_TIME, COLOR_WHITE, COLOR_BLUE, A_NORMAL); } /* Set default colors for black and white terminal. */ static void set_bw_colors () { colors[CLR_BACKGROUND] = A_NORMAL; colors[CLR_FRAME] = A_NORMAL; colors[CLR_WIN_TITLE] = A_NORMAL; colors[CLR_MENU_ITEM_DIR] = A_NORMAL; colors[CLR_MENU_ITEM_DIR_SELECTED] = A_REVERSE; colors[CLR_MENU_ITEM_PLAYLIST] = A_NORMAL; colors[CLR_MENU_ITEM_PLAYLIST_SELECTED] = A_REVERSE; colors[CLR_MENU_ITEM_FILE] = A_NORMAL; colors[CLR_MENU_ITEM_FILE_SELECTED] = A_REVERSE; colors[CLR_MENU_ITEM_FILE_MARKED] = A_BOLD; colors[CLR_MENU_ITEM_FILE_MARKED_SELECTED] = A_BOLD | A_REVERSE; colors[CLR_MENU_ITEM_INFO] = A_NORMAL; colors[CLR_MENU_ITEM_INFO_SELECTED] = A_REVERSE; colors[CLR_MENU_ITEM_INFO_MARKED] = A_BOLD; colors[CLR_MENU_ITEM_INFO_MARKED_SELECTED] = A_BOLD | A_REVERSE; colors[CLR_STATUS] = A_NORMAL; colors[CLR_TITLE] = A_BOLD; colors[CLR_STATE] = A_BOLD; colors[CLR_TIME_CURRENT] = A_BOLD; colors[CLR_TIME_LEFT] = A_BOLD; colors[CLR_TIME_TOTAL_FRAMES] = A_NORMAL; colors[CLR_TIME_TOTAL] = A_BOLD; colors[CLR_SOUND_PARAMS] = A_BOLD; colors[CLR_LEGEND] = A_NORMAL; colors[CLR_INFO_DISABLED] = A_BOLD; colors[CLR_INFO_ENABLED] = A_BOLD; colors[CLR_MIXER_BAR_EMPTY] = A_NORMAL; colors[CLR_MIXER_BAR_FILL] = A_REVERSE; colors[CLR_TIME_BAR_EMPTY] = A_NORMAL; colors[CLR_TIME_BAR_FILL] = A_REVERSE; colors[CLR_ENTRY] = A_NORMAL; colors[CLR_ENTRY_TITLE] = A_BOLD; colors[CLR_ERROR] = A_BOLD; colors[CLR_MESSAGE] = A_BOLD; colors[CLR_PLIST_TIME] = A_NORMAL; } static void theme_parse_error (const int line, const char *msg) { interface_fatal ("Parse error in theme file line %d: %s", line, msg); } /* Find the index of a color element by name. Return CLR_WRONG if not found. */ static enum color_index find_color_element_name (const char *name) { size_t ix; static struct { char *name; enum color_index idx; } color_tab[] = { { "background", CLR_BACKGROUND }, { "frame", CLR_FRAME }, { "window_title", CLR_WIN_TITLE }, { "directory", CLR_MENU_ITEM_DIR }, { "selected_directory", CLR_MENU_ITEM_DIR_SELECTED }, { "playlist", CLR_MENU_ITEM_PLAYLIST }, { "selected_playlist", CLR_MENU_ITEM_PLAYLIST_SELECTED }, { "file", CLR_MENU_ITEM_FILE }, { "selected_file", CLR_MENU_ITEM_FILE_SELECTED }, { "marked_file", CLR_MENU_ITEM_FILE_MARKED }, { "marked_selected_file", CLR_MENU_ITEM_FILE_MARKED_SELECTED }, { "info", CLR_MENU_ITEM_INFO }, { "selected_info", CLR_MENU_ITEM_INFO_SELECTED }, { "marked_info", CLR_MENU_ITEM_INFO_MARKED }, { "marked_selected_info", CLR_MENU_ITEM_INFO_MARKED_SELECTED }, { "status", CLR_STATUS }, { "title", CLR_TITLE }, { "state", CLR_STATE }, { "current_time", CLR_TIME_CURRENT }, { "time_left", CLR_TIME_LEFT }, { "total_time", CLR_TIME_TOTAL }, { "time_total_frames", CLR_TIME_TOTAL_FRAMES }, { "sound_parameters", CLR_SOUND_PARAMS }, { "legend", CLR_LEGEND }, { "disabled", CLR_INFO_DISABLED }, { "enabled", CLR_INFO_ENABLED }, { "empty_mixer_bar", CLR_MIXER_BAR_EMPTY }, { "filled_mixer_bar", CLR_MIXER_BAR_FILL }, { "empty_time_bar", CLR_TIME_BAR_EMPTY }, { "filled_time_bar", CLR_TIME_BAR_FILL }, { "entry", CLR_ENTRY }, { "entry_title", CLR_ENTRY_TITLE }, { "error", CLR_ERROR }, { "message", CLR_MESSAGE }, { "plist_time", CLR_PLIST_TIME } }; assert (name != NULL); for (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) { if (!strcasecmp(color_tab[ix].name, name)) return color_tab[ix].idx; } return CLR_WRONG; } /* Find the curses color by name. Return -1 if the color is unknown. */ static short find_color_name (const char *name) { size_t ix; static struct { char *name; short color; } color_tab[] = { { "black", COLOR_BLACK }, { "red", COLOR_RED }, { "green", COLOR_GREEN }, { "yellow", COLOR_YELLOW }, { "blue", COLOR_BLUE }, { "magenta", COLOR_MAGENTA }, { "cyan", COLOR_CYAN }, { "white", COLOR_WHITE }, { "default", COLOR_DEFAULT }, { "grey", COLOR_GREY } }; for (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) { if (!strcasecmp(color_tab[ix].name, name)) return color_tab[ix].color; } return -1; } static int new_colordef (const int line_num, const char *name, const short red, const short green, const short blue, const int errors_are_fatal) { short color = find_color_name (name); if (color == -1) { if (errors_are_fatal) theme_parse_error (line_num, "bad color name"); return 0; } if (can_change_color()) init_color (color, red, green, blue); return 1; } /* Find path to the theme for the given name. Returned memory is static. */ static char *find_theme_file (const char *name) { int rc; static char path[PATH_MAX]; path[sizeof(path)-1] = 0; if (name[0] == '/') { /* Absolute path */ strncpy (path, name, sizeof(path)); if (path[sizeof(path)-1]) interface_fatal ("Theme path too long!"); return path; } /* Try the user directory */ rc = snprintf(path, sizeof(path), "%s/%s", create_file_name("themes"), name); if (rc >= ssizeof(path)) interface_fatal ("Theme path too long!"); if (file_exists(path)) return path; /* Try the system directory */ rc = snprintf(path, sizeof(path), "%s/%s", SYSTEM_THEMES_DIR, name); if (rc >= ssizeof(path)) interface_fatal ("Theme path too long!"); if (file_exists(path)) return path; /* File related to the current directory? */ strncpy (path, name, sizeof(path)); if (path[sizeof(path)-1]) interface_fatal ("Theme path too long!"); return path; } /* Parse a theme element line. strtok() should be already invoked and consumed * the element name. * On error: if errors_are_fatal is true, * theme_parse_error() is invoked, otherwise 0 is returned. */ static int parse_theme_element (const int line_num, const char *name, const int errors_are_fatal) { char *tmp; char *foreground, *background, *attributes; attr_t curses_attr = 0; enum color_index element; short clr_fore, clr_back; if (!(tmp = strtok(NULL, " \t")) || strcmp(tmp, "=")) { if (errors_are_fatal) theme_parse_error (line_num, "expected '='"); return 0; } if (!(foreground = strtok(NULL, " \t"))) { if (errors_are_fatal) theme_parse_error (line_num, "foreground color not specified"); return 0; } if (!(background = strtok(NULL, " \t"))) { if (errors_are_fatal) theme_parse_error (line_num, "background color not specified"); return 0; } if ((attributes = strtok(NULL, " \t"))) { char *attr; if ((tmp = strtok(NULL, " \t"))) { if (errors_are_fatal) theme_parse_error (line_num, "unexpected chars at the end of line"); return 0; } attr = strtok (attributes, ","); do { if (!strcasecmp(attr, "normal")) curses_attr |= A_NORMAL; else if (!strcasecmp(attr, "standout")) curses_attr |= A_STANDOUT; else if (!strcasecmp(attr, "underline")) curses_attr |= A_UNDERLINE; else if (!strcasecmp(attr, "reverse")) curses_attr |= A_REVERSE; else if (!strcasecmp(attr, "blink")) curses_attr |= A_BLINK; else if (!strcasecmp(attr, "dim")) curses_attr |= A_DIM; else if (!strcasecmp(attr, "bold")) curses_attr |= A_BOLD; else if (!strcasecmp(attr, "protect")) curses_attr |= A_PROTECT; else { if (errors_are_fatal) theme_parse_error (line_num, "unknown attribute"); return 0; } } while ((attr = strtok(NULL, ","))); } if ((element = find_color_element_name(name)) == CLR_WRONG) { if (errors_are_fatal) theme_parse_error (line_num, "unknown element"); return 0; } if ((clr_fore = find_color_name(foreground)) == -1) { if (errors_are_fatal) theme_parse_error (line_num, "bad foreground color name"); return 0; } if ((clr_back = find_color_name(background)) == -1) { if (errors_are_fatal) theme_parse_error (line_num, "bad background color name"); return 0; } make_color (element, clr_fore, clr_back, curses_attr); return 1; } /* Parse a color value. strtok() should be already invoked and should "point" * to the number. If errors_are_fatal, use theme_parse_error() on error, * otherwise return -1. */ static short parse_rgb_color_value (const int line_num, const int errors_are_fatal) { char *tmp; char *end; long color; if (!(tmp = strtok(NULL, " \t"))) { if (errors_are_fatal) theme_parse_error (line_num, "3 color values expected"); return -1; } color = strtol (tmp, &end, 10); if (*end) { if (errors_are_fatal) theme_parse_error (line_num, "color value is not a valid number"); return -1; } if (!RANGE(0, color, 1000)) { if (errors_are_fatal) theme_parse_error (line_num, "color value should be in range 0-1000"); return -1; } return color; } /* Parse a theme color definition. strtok() should be already invoked and * consumed 'colordef'. On error: if errors_are_fatal is true, * theme_parse_error() is invoked, otherwise 0 is returned. */ static int parse_theme_colordef (const int line_num, const int errors_are_fatal) { char *name; char *tmp; short red, green, blue; if (!(name = strtok(NULL, " \t"))) { if (errors_are_fatal) theme_parse_error (line_num, "expected color name"); return 0; } if (!(tmp = strtok(NULL, " \t")) || strcmp(tmp, "=")) { if (errors_are_fatal) theme_parse_error (line_num, "expected '='"); return 0; } red = parse_rgb_color_value (line_num, errors_are_fatal); green = parse_rgb_color_value (line_num, errors_are_fatal); blue = parse_rgb_color_value (line_num, errors_are_fatal); if (red == -1 || green == -1 || blue == -1) return 0; if (!new_colordef(line_num, name, red, green, blue, errors_are_fatal)) return 0; return 1; } /* The lines should be in format: * * ELEMENT = FOREGROUND BACKGROUND [ATTRIBUTE[,ATTRIBUTE,..]] * or: * colordef COLORNAME = RED GREEN BLUE * * Blank lines and beginning with # are ignored, see example_theme. * * On error: if errors_are_fatal is true, interface_fatal() is invoked, * otherwise 0 is returned. */ static int parse_theme_line (const int line_num, char *line, const int errors_are_fatal) { char *name; if (line[0] == '#' || !(name = strtok(line, " \t"))) { /* empty line or a comment */ return 1; } if (!strcasecmp(name, "colordef")) return parse_theme_colordef (line_num, errors_are_fatal); return parse_theme_element (line_num, name, errors_are_fatal); } /* Load a color theme. If errors_are_fatal is true, errors cause * interface_fatal(), otherwise 0 is returned on error. */ static int load_color_theme (const char *name, const int errors_are_fatal) { FILE *file; char *line; int result = 1; int line_num = 0; char *theme_file = find_theme_file (name); if (!(file = fopen(theme_file, "r"))) { if (errors_are_fatal) interface_fatal ("Can't open theme file: %s", xstrerror (errno)); return 0; } while (result && (line = read_line (file))) { line_num++; result = parse_theme_line (line_num, line, errors_are_fatal); free (line); } fclose (file); return result; } static void reset_colors_table () { int i; pair_count = 1; for (i = 0; i < CLR_LAST; i++) colors[i] = -1; } void theme_init (bool has_xterm) { reset_colors_table (); if (has_colors ()) { char *file; if ((file = options_get_str ("ForceTheme"))) { load_color_theme (file, 1); strncpy (current_theme, find_theme_file (file), PATH_MAX); } else if (has_xterm && (file = options_get_str ("XTermTheme"))) { load_color_theme (file, 1); strncpy (current_theme, find_theme_file (file), PATH_MAX); } else if ((file = options_get_str ("Theme"))) { load_color_theme (file, 1); strncpy (current_theme, find_theme_file (file), PATH_MAX); } else snprintf (current_theme, PATH_MAX, "%s/example_theme", SYSTEM_THEMES_DIR); set_default_colors (); } else set_bw_colors (); } int get_color (const enum color_index index) { return colors[index]; } void themes_switch_theme (const char *file) { if (has_colors()) { reset_colors_table (); if (!load_color_theme(file, 0)) { interface_error ("Error loading theme!"); reset_colors_table (); } else strncpy (current_theme, file, PATH_MAX); set_default_colors (); } } const char *get_current_theme () { return current_theme; } moc-2.6.0~svn-r3005/README0000664000076400000500000003652613012723425014022 0ustar riesebiesrc MOC m u s i c o n c o n s o l e http://moc.daper.net/ -------------------------------------------------------------------------------- What Is It? -------------------------------------------------------------------------------- MOC (music on console) is a console audio player for LINUX/UNIX designed to be powerful and easy to use. You just need to select a file from some directory using the menu similar to Midnight Commander, and MOC will start playing all files in this directory beginning from the chosen file. There is no need to create playlists as in other players. If you want to combine some files from one or more directories in one playlist, you can do this. The playlist will be remembered between runs or you can save it as an m3u file to load it whenever you want. Need the console where MOC is running for more important things? Need to close the X terminal emulator? You don't have to stop playing - just press q and the interface will be detached leaving the server running. You can attach it later, or you can attach one interface in the console, and another in the X terminal emulator, no need to switch just to play another file. MOC plays smoothly, regardless of system or I/O load because it uses the output buffer in a separate thread. The transition between files is gapless, because the next file to be played is precached while the current file is playing. Supported file formats are: MP3, Ogg Vorbis, FLAC, Musepack (mpc), Speex, Opus, WAVE, those supported by FFmpeg/LibAV (e.g., WMA, RealAudio, AAC, MP4), AIFF, AU, SVX, Sphere Nist WAV, IRCAM SF, Creative VOC, SID, wavpack, MIDI and modplug. Other features: - Simple mixer - Color themes - Menu searching (playlist or directory) like M-s in Midnight Commander - The way MOC creates titles from tags is configurable - Optional character set conversion for file tags using iconv() - OSS, ALSA, SNDIO and JACK output - User defined keys - Cache for files' tags -------------------------------------------------------------------------------- Documentation and The MOC Forum -------------------------------------------------------------------------------- This file is only a brief description of MOC, for more information is available on the home page (http://moc.daper.net/documentation). You can also find a discussion forum on the MOC home page. -------------------------------------------------------------------------------- What Software Is Required To Build It? -------------------------------------------------------------------------------- To build MOC from the distribution tarball you will need: - A POSIX.1-2001 compatible UNIX system with POSIX threads (e.g., Linux or OSX) - A C compiler which is C99 capable and a C++ compiler (MOC is written in C, but libtool and some decoder plugins require a C++ compiler) - ncurses (probably already installed in your system) - POPT (libpopt) (probably already installed in your system) - Berkeley DB (libdb) version 4.1 (unless configured with --disable-cache) - GnuPG (gpg) if you are going to verify the tarball (and you should) If you are building from the SVN repository you will also need: - Subversion or git-svn (to checkout the source directory tree) - Autoconf version 2.64 and the associated Automake and Libtool You should choose which of the following audio formats you wish to play and provide the libraries needed to support them: - AAC - libfaad2 version 2.7 (http://www.audiocoding.com/), and libid3tag (http://www.underbit.com/products/mad/) - FLAC - libFLAC version 1.1.3 (http://flac.sourceforge.net/) - MIDI - libtimidity version 0.1 (http://timidity.sourceforge.net/) - modplug - libmodplug version 0.7 (http://modplug-xmms.sourceforge.net/) - MP3 - libmad with libid3tag (ftp://ftp.mars.org/pub/mpeg/) - Musepack (mpc) - libmpc (http://www.musepack.net/), and - taglib version 1.3.1 (http://developer.kde.org/~wheeler/taglib.html) - Ogg Vorbis - libvorbis, libogg and libvorbisfile (all version 1.0) (http://www.xiph.org/ogg/), or - libvorbisidec and libogg (both version 1.0) (http://svn.xiph.org/trunk/Tremor) - SID - libsidplay2 version 2.1.1 and libsidutils version 1.0.4 (http://sidplay2.sourceforge.net/) - Speex - libspeex version 1.0 (http://www.speex.org/), and - libogg version 1.0 (http://www.xiph.org/ogg/) - WMA, RealAudio (.ra), MP4 - FFmpeg version 1.0 (http://www.ffmpeg.org/), or - LibAV version 10.0 (http://www.libav.org/) - WAVE, AU, AIFF, SVX, SPH, IRC, VOC - libsndfile version 1.0 (http://www.mega-nerd.com/libsndfile/) - wavpack - libwavpack version 4.31 (http://www.wavpack.com/) For interfacing to the sound sub-system, you will need libraries for one or more of the following: - ALSA - alsa-lib version 1.0.11 (http://www.alsa-project.org/) - OSS - the OSS libraries (http://www.opensound.com/) - BSD's SNDIO - SNDIO libraries - JACK low-latency audio server - JACK version 0.4 (http://jackit.sourceforge.net/) For network streams: - libcurl version 7.15.1 (http://curl.haxx.se/) For resampling (playing files with sample rate not supported by your hardware): - libresamplerate version 0.1.2 (http://www.mega-nerd.com/SRC/) For librcc (fixes encoding in broken mp3 tags): - http://rusxmms.sourceforge.net/ Note that for Debian-based distributions, you will also require any '-dev' suffixed versions of the packages above if building from source. The versions given above are minimum versions and later versions should also work. However, MOC may not yet have caught up with the very latest changes to library interfaces and these may cause problems if they break backwards compatibility. -------------------------------------------------------------------------------- On Which Systems Is MOC Running? -------------------------------------------------------------------------------- MOC is developed and tested on GNU/Linux. MOC is now C99 and POSIX.1-2001 compliant and so should build and run on any system which has a C99 capable compiler and is POSIX.1-2001 compatible. However, there may still be cases where MOC breaks this compliance and any reports of such breakage are welcome. There is no intention to support MOC on MS-Windows (so please don't ask). -------------------------------------------------------------------------------- How Do I Verify the Authenticity of the Tarball? -------------------------------------------------------------------------------- If you downloaded the official MOC distribution you should have the following files: moc-2.6-alpha3.tar.asc moc-2.6-alpha3.tar.md5 moc-2.6-alpha3.tar.xz would check the integrity of the download: md5sum -c moc-2.6-alpha3.tar.md5 and then verify the tarball thusly: xzcat moc-2.6-alpha3.tar.xz | gpg --verify moc-2.6-alpha3.tar.asc - The signature file (*.asc) was made against the uncompressed tarball, so if the tarball you have has been recompressed using a tool other than XZ then uncompress it using the appropriate tool then verify that: gunzip moc-2.6-alpha3.tar.gz gpg --verify moc-2.6-alpha3.tar.asc If the tool can output to stdout (and most can) then you can also verify verify it in a single step similar to the way in which the XZ tarball was verified above. Of course, you'll also need the MOC Release Signing Key: gpg --recv-key 0x2885A7AA for which the fingerprint is: 5935 9B80 406D 9E73 E805 99BE F312 1E4F 2885 A7AA -------------------------------------------------------------------------------- How Do I Build and Install It? -------------------------------------------------------------------------------- Generic installation instruction is included in the INSTALL file. In short, if you are building from an SVN checkout of MOC (but not if you are building from a downloaded tarball) then you will first need to run: autoreconf -if and then proceed as shown below for a tarball. (If you are using the tarball but have applied additional patches then you may also need to run autoreconf.) To build MOC from a downloaded tarball just type: ./configure make And as root: make install Under FreeBSD and NetBSD (and possibly other systems) it is necessary to run the configure script this way: ./configure LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include In addition to the standard configure options documented in the INSTALL file, there are some MOC-specific options: --enable-cache=[yes|no] Specifying 'no' will disable the tags cache support. If your intent is to remove the Berkeley DB dependancy (rather than simply removing the on-disk cache) then you should also either build MOC without RCC support or use a librcc built with BDB disabled. --enable-debug=[yes|no|gdb] Using 'gdb' will cause MOC to be built with options tailored to use with GDB. (Note that in release 2.6 this option will be split into separate debugging and logging options.) --with-oss=[yes|no|DIR] Where DIR is the location of the OSS include directory (and defaults to '/usr/lib/oss'). --with-vorbis=[yes|no|tremor] Using 'tremor' will cause MOC to build against the integer-only implementation of the Vorbis library (libvorbisidec). You can install MOC into its own source directory tree and run it from there so you do not have to install it permanently on your system. If you're just wanting to try it out or test some patches, then this is something you may wish to do: ./configure --prefix="$PWD" --without-timidity make make install bin/mocp -M .moc -------------------------------------------------------------------------------- How Do I Use It? -------------------------------------------------------------------------------- Run program with the 'mocp' command. The usage is simple; if you need help, press 'h' and/or read mocp manpage. There is no complicated command line or cryptic commands. Using MOC is as easy as using basic functions of Midnight Commander. You can use a configuration file placed in ~/.moc/config, but it's not required. See config.example provided with MOC. -------------------------------------------------------------------------------- Using Themes -------------------------------------------------------------------------------- Yes, there are themes, because people wanted them. :) Themes can change all colors and only colors. An example theme file with a exhaustive description is included (themes/example_theme) and is the default MOC appearance. Theme files should be placed in ~/.moc/themes/ or $(datadir)/moc/themes/ (e.g., /usr/local/share/moc/themes) directory, and can be selected with the Theme configuration options or the -T command line option (see the manpage and the example configuration file). Feel free to share the themes you have created. -------------------------------------------------------------------------------- Defining Keys -------------------------------------------------------------------------------- You can redefine standard keys. See the instructions in the keymap.example file. -------------------------------------------------------------------------------- How Do I Report A Problem? -------------------------------------------------------------------------------- Not every release is extensively tested on every system, so the particular configuration of software, libraries, versions and hardware on your system might expose a problem. If you find any problems then you should search the MOC Forum for a solution; your problem may not be unique. If you do find an existing topic which matches your problem but does not offer a solution, or the solution offered does not work for you and the topic appears still active, then please add your experience to it; it may be that additional information you can provide will contain the clue needed to resolve the problem. If you don't find an answer there and you installed MOC from your Linux distribution's repository then you should report it via your distribution's usual reporting channels in the first instance. If the problem is ultimately identified as actually being in MOC itself, it should then be reported to the MOC Maintainer (preferably by the distribution's MOC package maintainer). If you built MOC from source yourself or you get no resolution from your distribution then start a new topic on the MOC Forum for your problem or contact the MOC Maintainer. Before reporting a problem, you should first read this Forum post: Linkname: How to Report Bugs Effectively URL: http://moc.daper.net/node/1035 and the essay it references: Linkname: How to Report Bugs Effectively URL: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html There are two things you must do if at all possible: 1. Make sure you are using the current stable MOC release or, even better, can reproduce it on the latest development release or SVN HEAD, and 2. Make sure you include the version and revision information (which you can obtain by running 'mocp --version'). If you do not do those two things (and don't offer a good explanation as to why you didn't) your problem report is likely to be ignored until such time as you do. -------------------------------------------------------------------------------- Hacking -------------------------------------------------------------------------------- Want to modify MOC? You're welcome to do so, and patch contributions are also welcome. MOC is written in C, so you must at least know this language to make simple changes. It is multi-threaded program, but there are places where you don't need to worry about that (the interface is only a single thread process). It uses autoconf, automake and libtool chain to generate configuration/compilation stuff, so you must know how to use it, for example, if you need to link to an additional library. The documentation for some parts of the internal API for creating decoder plugins (file format support) and sound output drivers can be generated using Doxygen (http://www.doxygen.org/). Just run the doxygen command from the MOC source directory. Before you change anything it is a good idea to check for the latest development version (check out from the Subversion repository is the best). Your changes might conflict with changes already made to the source or your feature might be already implemented. See also the TODO file as it is updated regularly and contains quite detailed information on future plans. If you need help, just contact MOC's Maintainer via e-mail. And if you are planning anything non-trivial it's a good idea to discuss your intentions with the MOC Maintainer once you've clarified your ideas but before spending too much time implementing them; it will be more productive if your work fits with MOC's future direction. -------------------------------------------------------------------------------- Who Wrote It? Where Can I Send Bug Reports, Questions or Comments? -------------------------------------------------------------------------------- * Original author is Damian Pietras * Current maintainer is John Fitzgerald * For comments and questions see the official forum: http://moc.daper.net/forum * Need to report a bug? You can reach the maintainer(s) at: -------------------------------------------------------------------------------- moc-2.6.0~svn-r3005/utf8.h0000664000076400000500000000216312424322452014170 0ustar riesebiesrc#ifndef UTF8_H #define UTF8_H #if defined HAVE_NCURSESW_CURSES_H # include #elif defined HAVE_NCURSESW_H # include #elif defined HAVE_NCURSES_CURSES_H # include #elif defined HAVE_NCURSES_H # include #elif defined HAVE_CURSES_H # include #endif #include #ifdef HAVE_ICONV # include #endif #ifdef __cplusplus extern "C" { #endif /* parameter passed to wcswidth() as a maximum width */ #define WIDTH_MAX 2048 void utf8_init (); void utf8_cleanup (); int xwaddstr (WINDOW *win, const char *str); int xwaddnstr (WINDOW *win, const char *str, const int n); int xmvwaddstr (WINDOW *win, const int y, const int x, const char *str); int xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str, const int n); int xwprintw (WINDOW *win, const char *fmt, ...) ATTR_PRINTF(2, 3); size_t strwidth (const char *s); char *xstrtail (const char *str, const int len); char *iconv_str (const iconv_t desc, const char *str); char *files_iconv_str (const char *str); char *xterm_iconv_str (const char *str); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/README_equalizer0000664000076400000500000001537711537550300016104 0ustar riesebiesrcPreamble --- This document is meant to give you an overview on the idea of having a parametric equalizer for sound enhancement and how you can create your own presets. Also the interaction with the equalizer in MOC is described. I would like to improve this document to make it more usable; so if you have any comments and/or ideas feel free to contact me. - Hendrik Iben (hibentzi(dot)de) Content --- 0. Document History 1. Motivation 2. Usage 3. Preset Format 4. Creating Presets 5. TODO 6. References 0. Document History --- 07.09.2008 - Initial version 15.03.2011 - Reformatted 1. Nuts and Bolts / Motivation for Implementing the Equalizer --- The equalizer is an implementation of a biquadratic peaking equalizer filter looked up from the Audio EQ Cookbook[1]. It happens to be a parametric equalizer and this means that, different from other equalizer implementations, the number of bands* is not fixed. When I started the idea of implementing the equalizer I looked around in the source of other audio playback software and found that a lot of them are recycling the code used by the famous XMMS[2] audio player. I also would have liked to recycle the code but I decided against it for two reasons: The first reason is that there is almost no documentation on the algorithm used. Maybe the signal processing folks have fun finding out what makes this thing work but I was totally lost. So I decided that I wanted to *know* what I am doing if I do it. As for the second reason, the code used by XMMS is totally optimized for integer arithmetic. There is no problem with this in general but I had the goal of implementing something that was as accurate as I could and I wanted to use floating point arithmetic. So I am no signals processing guy, but I have -- I think -- a solid understanding of the matter. I sat down and started to read about equalizing, audio processing and signal theory in general. After some time I found a mathematical description and a C implementation of biquadratic filters in the Audio Cookbook. I made an implementation of the XMMS equalizer and the biquadratic filter using Octave[3] to compare the outcome of both filters. I was a bit surprised how different filters can be but in the end succeeded (?) in finding a quite good biquadratic filter set that would produce results not unlike the XMMS equalizer. Although I did not use the XMMS-code I think that people will be more happy to accept this equalizer if they can use their presets with it. There is some conversion needed, but it's a straightforward process. I converted all presets provided by XMMS into presets for this mixer. They should be available at [4]. * A band is a chosen center frequency where a filter has most impact. If you look at WinAmp / XMMS / Beep Media Player you will find that they settled on a common set of 10 bands. 2. Using the Equalizer --- The default keys for the equalizer are: 'e' - Refresh equalizer 'E' - Toggle equalizer (on/off) 'k' - Select next preset 'K' - Select previous preset Each of these actions results in a message displayed in the message area. This message will be overridden by the next action. 3. Preset Format --- Presets for the equalizer are to be placed in a directory called 'eqsets' in MOC's home directory (e.g., $HOME/.moc/eqsets). There is no convention for the filename, but it will serve as the name in the selection process. File format in pseudo EBNF: EQSET (( )|(0 ))* CF: Center frequency (sane values are from ~20 to ~20000). BW: Bandwith in Octaves. This defines how fast the bands influence vanishes over the frequencies. AMP: Amplification factor (in dB) to apply to the band. PREAMP: Specifies an amplification factor applied before equalizing. So a valid equalizer set would be: # this is a comment EQSET # amplify audio by 1.4dB 0 1.4 # damp frequencies at 100Hz by -4dB, filter bandwidth 1.5 octaves 100 1.5 -4 # amplify frequencies at 4000Hz by 2dB, filter bandwidth 1.5 octaves 4000 1.5 2 There is no order to stick to when specifying frequencies. 4. Creating Your Own Presets --- For a start you should have a look at the converted presets[4]. The bandwidths used in the conversion have been extracted by taking a look at the filters signal response (implementation and analysis in Octave). I tried to do this as accurately as possible but I don't know if I made a mistake. They sound correct though... :-) You might note that there is never a positive amplification factor in the presets although there are in the original preset. The reason for this is that I used the maximum amplification in the preset as zero amplification and adjusted the other values accordingly. In general, when creating a preset get used to the following idea: Do not amplify the frequencies you want but damp those that are of no interest. This has the same effect but avoids clipping and this equalizer type seems to be very prone to clipping. Also be very careful with pre-amplifying the audio for the same reason. With that said, the next confusing thing is the bandwidth definition. Every band needs a defined bandwidth in octaves where the bandwidth defines where the filter's effect has been reduced by 3dB*. This means that if you define a band at 1000Hz with a bandwidth of 1.5 octaves and an amplification of -10dB, at 353.6Hz** and at 2828.4Hz the amplification will be reduced to -7dB. If unsure, stay in between 1.0 and 2.0. Just keep in mind that if two bands overlap you might get an undesired amplification. When designing presets, just save the preset and select it in MOC. After each change press the refresh key (default 'e'). This will re-create the equalizer reflecting your changes. If your preset is not found, have a look at the output of MOC's server thread. Parsing errors are emitted there. * 3dB is commonly used for bandwidth. -3dB equals about 70.7% of original amplification. ** 353.6 =~ 1000*(2^-1.5), 2828.4 =~ 1000*(2^1.5) 5. TODO --- - The equalizer is currently not optimized in any way. - It converts all sound data into floating point values to perform the equalization and converts them back afterwards. A better approach would be either to provide integer algorithms for equalization or to leave the audio data in floating point format. - There is no sorting for the presets; their order is defined by reading the directory content. - Maybe it would be nice to add a name to the preset different from the filename. 6. References --- [1] Cookbook formulae for audio EQ biquad filter coefficients http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt [2] X Multimedia System http://www.xmms.org/ [3] GNU Octave http://www.gnu.org/software/octave/ [4] Converted WinAmp / XMMS Equalizer sets http://www.informatik.uni-bremen.de/~hiben/moc/eqsets.tar.gz moc-2.6.0~svn-r3005/tools/0002775000076400000500000000000013710016723014271 5ustar riesebiesrcmoc-2.6.0~svn-r3005/tools/README0000664000076400000500000000310211721557223015150 0ustar riesebiesrc MOC TOOLS DIRECTORY 1. Introduction This directory contains debugging and testing tools which are intended for developers and maintainers. 2. The Tools 2.1 MD5 Check The 'md5check.sh' script is intended to verify that the samples which arrive at MOC's player code are the same as those produced by the library the decoder is using. It does this by having MOC compute the MD5 sum of the audio file as it is playing it and writing the result to the server's log file. The log file is then read by the 'md5check.sh' tool and the MD5 sum checked against one computed from the audio file using the library's native decoding program. It is important to use the program associated with the decoder library if one exists because other programs may not produce exactly the same samples, particularly for lossy formats. It is also possible that bugs in the library mean the samples produced are not correct, but the tool is intended to test the passage of the samples through the driver and not the fidelity of the library used. 2.2 Test File Generation The 'maketests.sh' script generates test files in the directory in which it is run. The test files are 10 seconds of sinewave audio generated by SoX. Each file's name is in a format which represents its characteristics and these can be checked against those MOC uses with the '-e' option of 'md5scheck.sh'. All filenames start with 'sinewave-' and the script will refuse to run if any files starting with that name already exist. It is wise to run this script in an empty directory. It generates a lot of files. moc-2.6.0~svn-r3005/tools/maketests.sh0000775000076400000500000001121212221116404016614 0ustar riesebiesrc#!/bin/bash # # MOC - music on console # Copyright (C) 2004-2005 Damian Pietras # # maketests.sh Copyright (C) 2012 John Fitzgerald # # 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. # # # TODO: - Add other supported formats. # AUDIO=false VIDEO=false FFMPEG="$(which avconv 2>/dev/null || which ffmpeg 2>/dev/null)" SOX="$(which sox 2>/dev/null)" SYNTH="synth 10 sine 440 vol 0.5" # Clean error termination. function die { echo $@ > /dev/stderr exit 1 } [[ -x "$SOX" ]] || die This script requires the SoX package. # Provide usage information. function usage () { echo "Usage: ${0##*/} [-a] [--audio] [-h] [--video] [FORMAT ...]" } # Provide help information. function help () { echo echo "MOC test file generation tool" echo usage echo echo " -a|--all Generate all formats" echo " --audio Generate all audio formats" echo " -h|--help This help information" echo " --video Generate all video formats" echo echo "Supported audio formats: flac mp3 vorbis wav" echo "Supported video formats: vob" echo } # Generate FLAC audio test files. function flac { echo "Generating FLAC audio test files" for r in 8000 16000 24000 32000 48000 96000 192000 \ 11025 22050 44100 88200 176400 do for b in 16 32 do $SOX -b$b -c1 -r$r -e signed -n -L sinewave-s${b}le-1-$r.flac $SYNTH $SOX -b$b -c2 -r$r -e signed -n -L sinewave-s${b}le-2-$r.flac $SYNTH done done } # Generate MP3 audio test files. function mp3 { echo "Generating MP3 audio test files" for r in 8000 16000 24000 32000 48000 11025 22050 44100 do for c in 1 2 do $SOX -b8 -c$c -r$r -n sinewave-u8-$c-$r.mp3 $SYNTH for b in 16 24 32 do $SOX -b$b -c$c -r$r -n -L sinewave-s${b}le-$c-$r.mp3 $SYNTH $SOX -b$b -c$c -r$r -n -B sinewave-s${b}be-$c-$r.mp3 $SYNTH done done done } # Generate VOB video test files. function vob { [[ -x "$FFMPEG" ]] || return echo "Generating VOB video test files" for r in 16000 22050 24000 32000 44100 48000 do for c in 1 2 do $FFMPEG -f rawvideo -pix_fmt yuv420p -s 320x240 -r 30000/1001 \ -i /dev/zero \ -f s16le -c pcm_s16le -ac 2 -ar 48000 \ -i <($SOX -q -b16 -c2 -r 48000 -e signed -n -L -t s16 - $SYNTH) \ -vcodec mpeg2video -acodec mp2 -shortest -ac $c -ar $r \ -y sinewave-s16le-$c-$r.vob > /dev/null 2>&1 done done } # Generate Ogg/Vorbis audio test files. function vorbis { echo "Generating Ogg/Vorbis audio test files" for r in 8000 16000 24000 32000 48000 96000 192000 \ 11025 22050 44100 88200 176400 do $SOX -b16 -c1 -r$r -e signed -n -L sinewave-s16le-1-$r.ogg $SYNTH $SOX -b16 -c2 -r$r -e signed -n -L sinewave-s16le-2-$r.ogg $SYNTH done } # Generate WAV audio test files. function wav { echo "Generating WAV audio test files" for r in 8000 16000 24000 32000 48000 96000 192000 \ 11025 22050 44100 88200 176400 do for c in 1 2 do $SOX -b8 -c$c -r$r -n sinewave-u8-$c-$r.wav $SYNTH $SOX -b16 -c$c -r$r -n -B sinewave-s16be-$c-$r.wav $SYNTH for b in 16 24 32 do $SOX -b$b -c$c -r$r -n -L sinewave-s${b}le-$c-$r.wav $SYNTH done done done } # Directory safety check. ls sinewave-* > /dev/null 2>&1 && { echo echo "This script generates many filenames starting with 'sinewave-' in the" echo "current directory which already contains similarly named files." echo "Running it in this directory is a really, really bad idea. (In fact," echo "it's probably not wise to run this script in a non-empty directory at" echo "all.) So we're aborting in the interests of safety." echo exit 1 } > /dev/stderr # Process command line options. for OPTS do case $1 in -a|--all) AUDIO=true VIDEO=true ;; --audio) AUDIO=true ;; -h|--help) help exit 0 ;; --video) VIDEO=true ;; -*) echo Unrecognised option: $1 usage > /dev/stderr exit 1 ;; *) break ;; esac shift done # Generate all audio formats. $AUDIO && { flac mp3 vorbis wav } # Generate all video formats. $VIDEO && { vob } # Generate specified formats. for ARGS do case $1 in flac|mp3|vorbis|wav) $1 ;; vob) $1 ;; *) echo "*** Unsupported format: $1" echo ;; esac shift done exit 0 moc-2.6.0~svn-r3005/tools/md5check.sh0000775000076400000500000002021613225532471016316 0ustar riesebiesrc#!/bin/bash # # MOC - music on console # Copyright (C) 2004-2005 Damian Pietras # # md5check.sh Copyright (C) 2012 John Fitzgerald # # 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. # # # TODO: - Make format, rate and channels explicit where possible. # - Add remaining decoder check functions. # - Fix decoders with genuine MD5 mismatches. # - Fix decoders with genuine format mismatches. # - Handle format mismatches which aren't genuine. # - Possibly rewrite in Perl for speed. # declare -A UNKNOWN declare -A UNSUPPORTED EXTRA=false IGNORE=false RC=0 SILENT=false TREMOR=false VERBOSE=false # Clean error termination. function die { echo '***' $@ > /dev/stderr exit 1 } # Provide usage information. function usage () { echo "Usage: ${0##*/} [-e] [-s|-v] [LOGFILE]" echo " ${0##*/} -h" } # Provide help information. function help () { echo echo "MOC MD5 sum checking tool" echo usage echo echo " -e|--extra Perform extra checks" echo " -h|--help This help information" echo " -i|--ignore Ignore known problems" echo " -s|--silent Output no results" echo " -v|--verbose Output all results" echo echo " LOGFILE MOC server log file name, or '-' for stdin" echo " (default: 'mocp_server_log' in the current directory)" echo echo "Exit codes: 0 - No errors or mismatches" echo " 1 - Error occurred" echo " 2 - Mismatch found" echo } # Check the FAAD decoder's samples. FAAD=$(which faad 2>/dev/null) function aac () { local ENDIAN OPTS [[ -x "$FAAD" ]] || die faad2 not installed [[ "${FMT:0:1}" = "f" ]] && ENDIAN=le OPTS="-w -q -f2" [[ "${FMT:1:2}" = "16" ]] && OPTS="$OPTS -b1" [[ "${FMT:1:2}" = "24" ]] && OPTS="$OPTS -b2" [[ "${FMT:1:2}" = "32" ]] && OPTS="$OPTS -b3" SUM2=$($FAAD $OPTS "$FILE" | md5sum) LEN2=$($FAAD $OPTS "$FILE" | wc -c) } # Check the FFmpeg decoder's samples. FFMPEG=$(which avconv 2>/dev/null || which ffmpeg 2>/dev/null) function ffmpeg () { local ENDIAN OPTS [[ -x "$FFMPEG" ]] || die ffmpeg/avconv not installed [[ "${FMT:0:1}" = "f" ]] && ENDIAN=le OPTS="-ac $CHANS -ar $RATE -f $FMT$ENDIAN" SUM2="$($FFMPEG -i "$FILE" $OPTS - /dev/null | md5sum)" LEN2=$($FFMPEG -i "$FILE" $OPTS - /dev/null | wc -c) [[ "$($FFMPEG -i "$FILE" &1)" =~ Audio:\ .*\ (mono|stereo) ]] || \ IGNORE_SUM=$IGNORE } # Check the FLAC decoder's samples. SOX=$(which sox 2>/dev/null) function flac () { local OPTS [[ -x "$SOX" ]] || die "SoX (for flac) not installed" [[ "${FMT:0:1}" = "s" ]] && OPTS="-e signed" || OPTS="-e unsigned" [[ "${FMT:1:1}" = "8" ]] && OPTS="$OPTS -b8 -L" [[ "${FMT:1:2}" = "16" ]] && OPTS="$OPTS -b16" [[ "${FMT:1:2}" = "24" ]] && OPTS="$OPTS -b24" [[ "${FMT:1:2}" = "32" ]] && OPTS="$OPTS -b32" [[ "$FMT" =~ "le" ]] && OPTS="$OPTS -L" [[ "$FMT" =~ "be" ]] && OPTS="$OPTS -B" OPTS="$OPTS -r$RATE -c$CHANS" SUM2=$($SOX "$FILE" $OPTS -t raw - | md5sum) LEN2=$($SOX "$FILE" $OPTS -t raw - | wc -c) } # Check the Ogg/Vorbis decoder's samples. OGGDEC=$(which oggdec 2>/dev/null) function vorbis () { [[ -x "$OGGDEC" ]] || die oggdec not installed SUM2="$($OGGDEC -RQ -o - "$FILE" | md5sum)" LEN2=$($OGGDEC -RQ -o - "$FILE" | wc -c) } # Check the LibSndfile decoder's samples. SOX=$(which sox 2>/dev/null) function sndfile () { # LibSndfile doesn't have a decoder, use SoX. [[ -x "$SOX" ]] || die "sox (for sndfile) not installed" SUM2="$($SOX "$FILE" -t f32 - | md5sum)" LEN2=$($SOX "$FILE" -t f32 - | wc -c) [[ "$NAME" == *-s32le-* ]] && IGNORE_SUM=$IGNORE } # Check the MP3 decoder's samples. SOX=$(which sox 2>/dev/null) function mp3 () { # Lame's decoder only does 16-bit, use SoX. [[ -x "$SOX" ]] || die "sox (for mp3) not installed" SUM2="$($SOX "$FILE" -t s32 - | md5sum)" LEN2=$($SOX "$FILE" -t s32 - | wc -c) IGNORE_SUM=$IGNORE IGNORE_LEN=$IGNORE } # Check the Speex decoder's samples. SPEEX=$(which speexdec 2>/dev/null) function speex () { [[ -x "$SPEEX" ]] || die speexdec not installed SUM2="$($SPEEX "$FILE" - 2>/dev/null | md5sum)" LEN2=$($SPEEX "$FILE" - 2>/dev/null | wc -c) IGNORE_SUM=$IGNORE IGNORE_LEN=$IGNORE } # Process command line options. for OPTS do case $1 in -e|--extra) EXTRA=true ;; -i|--ignore) IGNORE=true ;; -v|--verbose) VERBOSE=true SILENT=false ;; -s|--silent) SILENT=true VERBOSE=false ;; -h|--help) help exit 0 ;; --|-) break ;; -*) echo Unrecognised option: $1 usage > /dev/stderr exit 1 ;; *) break ;; esac shift done # Allow for log file parameter. LOG="${1:-mocp_server_log}" [[ "$LOG" = "-" ]] && LOG=/dev/stdin # Output formatting. $SILENT || echo # Process server log file. while read do # Reject log file if circular logging has been used. [[ "$REPLY" =~ "Circular Log Starts" ]] && \ die MD5 sums cannot be checked when circular logging was used # Extract MOC revision header. [[ "$REPLY" =~ "This is Music On Console" ]] && \ REVN="$(expr "$REPLY" : '.*\(Music .*\)')" # Check for Tremor decoder. [[ "$REPLY" =~ Loaded\ [0-9]+\ decoders:.*vorbis\(tremor\) ]] && \ TREMOR=true # Extract file's full pathname. [[ "$REPLY" =~ "Playing item" ]] && \ FILE="$(expr "$REPLY" : '.* item [0-9]*: \(.*\)')" # Ignore all non-MD5 lines. [[ "$REPLY" =~ "MD5" ]] || continue # Extract fields of interest. NAME="$(expr "$REPLY" : '.*MD5(\([^)]*\))')" set -- $(expr "$REPLY" : '.*MD5([^)]*) = \(.*\)') SUM=$1 LEN=$2 $TREMOR && [[ "$3" = "vorbis" ]] && DEC=tremor || DEC=$3 FMT=$4 CHANS=$5 RATE=$6 # Check that we have the full pathname and it's not a dangling symlink. [[ "$NAME" = "$(basename "$FILE")" ]] || die Filename mismatch [[ -L "$FILE" && ! -f "$FILE" ]] && continue # Get the independant MD5 sum and length of audio file. case $DEC in aac|ffmpeg|flac|mp3|sndfile|speex|vorbis) IGNORE_LEN=false IGNORE_SUM=false $DEC SUM2=$(expr "$SUM2" : '\([^ ]*\)') ;; modplug|musepack|sidplay2|timidity|tremor|wavpack) $IGNORE && continue [[ "${UNSUPPORTED[$DEC]}" ]] || { echo -e "*** Decoder not yet supported: $DEC\n" > /dev/stderr UNSUPPORTED[$DEC]="Y" } continue ;; *) [[ "${UNKNOWN[$DEC]}" ]] || { echo -e "*** Unknown decoder: $DEC\n" > /dev/stderr UNKNOWN[$DEC]="Y" } continue ;; esac # Compare results. BADFMT=false $EXTRA && [[ "$NAME" == "sinewave-*" ]] && { set -- $(echo $NAME | tr '.-' ' ') FMT2=${2/24/32/} CHANS2=$3 RATE2=$4 [[ "$FMT" = "$FMT2" ]] || BADFMT=true [[ "$CHANS" = "$CHANS2" ]] || BADFMT=true [[ "$RATE" = "$RATE2" ]] || BADFMT=true } BADSUM=false; $IGNORE_SUM || [[ "$SUM" = "$SUM2" ]] || BADSUM=true BADLEN=false; $IGNORE_LEN || [[ "$LEN" = "$LEN2" ]] || BADLEN=true # Set exit code. $BADFMT || $BADSUM || $BADLEN && RC=2 # Determine output requirements. $SILENT && continue $BADFMT || $BADSUM || $BADLEN || $VERBOSE || continue # Report result. [[ "$REVN" ]] && { echo "Test Results for $REVN" echo REVN= } echo "$NAME:" echo " $SUM $LEN $DEC $FMT $CHANS $RATE" echo " $SUM2 $LEN2" $BADFMT && echo "*** Format mismatch" $BADSUM && echo "*** MD5 sum mismatch" $BADLEN && echo "*** Length mismatch" echo done < $LOG $SILENT || { case "$RV" in 1) echo "No mismatches found" ;; 2) echo "NOTE: This tool is still being refined. Do not accept mismatches" echo " at face value; they may be due to factors such as sample size" echo " differences. But it does provide a reason to investigate" echo " such mismatches further (and further refine this tool if false)." echo ;; esac } exit $RC moc-2.6.0~svn-r3005/THANKS0000664000076400000500000001741013012723417014045 0ustar riesebiesrcThanks to all people who have helped us make MOC better, suggesting changes or notifing about bugs: Alexis Ballier: * Adapt to FFmpeg's changed include directory layout. Rastislav Barlik: * Fixed segfault on deleted equalizer file. Daniel T. Borelli: * Added support for key to switch on/off the display of percent played. * Added a configuration option to set the initial percent played state. * Fixed miscellaneous coding errors. * Assisted with testing. * Provided basis for fixing foreground mode unreaped children. Morten Grunnet Buhl: * Provided Yellow/Red theme. Andreas Cadhalpun: * Resolved the deprecated 'request_channels' puzzle. Nuno Cardoso: * Options in a hash table. * Added -j command line option to jump to a given position. Josh Coalson: * Fixes for compilation with FLAC 1.1.3. Brent Cook: * Various OpenBSD-related patches. Niels Aan de Brugh: * Improved error detection for terminal height limits. Jonathan Derque: * Support for GNU screen title. Joseph Dunn: * Bug reports. * He gave me access to his FreeBSD box that allowed me to fix a bug. Dennis Felsing: * Fixed compilation of sidplay2. Filippo Giunchedi: * Added directories to the 'a' command. Alexey Gladkov: * Support for filenames and directory names recoding using librcc. Tomasz Golinski: * Assisted greatly with testing and debugging. * Headed the effort to port MOC to OpenWRT. * Provided signficant information on DTS, AAC and other formats. * Also contributed much time in the refinement of feature ideas. * Provided 24-bit format conversion bug fixes. * Fixed volume control problem. * Provided check for valid output channels and formats. Juho Hämäläinen: * Added -Q (--format) FORMAT_STRING option to display file information. Hendrik Iben: * Added TiMidity decoder for MIDI. * Added SidPlay2 decoder for SID. * Added Modplug decoder. * Added check for newer faad2-library (AAC). * Added software mixer. * Added parametric equalizer. * Merged parametric equalizer and mono-mixing code. * Fixed miscellaneous coding errors. * Fixed logging of deleted filenames. * Assisted with testing. Daniel Kalør: * Provided spelling fixes. * Fixed clearing chars when displaying file information. * Fixed field overflow when fast-forwarding yields very large bit rates. * Repositioned selected track to middle after scrolling off screen. * Fixed the symbol for "kilo", use "k" (lowercase). * Allowed seeking to beginning of a file in Vorbis/FLAC. Kari Karvonen: * Suggested code for the AutoNext option. Hasan Keler: * Assisted with testing. * Also assisted by commenting on feature ideas. Max Klinger: * Silenced compiler warnings on various platforms. * Migrated AAC decoder to later FAAD2 API. * Replaced GNU-specific getline() with read_line(). * Resolved JACK deprecation warnings. * Prompted option to autostart JACK if the server isn't running. * Assisted with testing. * Also assisted by commenting on feature ideas. Adam Kowalski: * Many bug reports (also tests). Florian Kriener: * Provided title building code. * Corrected many typos and fixed many bugs. Vladimir Krylov: * Added 24-bit support to the OSS sound driver. Maciej Kubiak: * Suggestions and bug reports. Géraud Le Falher: * Fixed crash in lyrics code with overly long filenames. * Display lyrics saved in files together with music. Jacek Lehmann: * Provided Green theme and fixes for a few terminals. Tero Marttila: * Changed minimum screen height to 7 lines. Gregory Maxwell: * Provided patch for off_t in io_* functions. Alex Merenstein: * Fixed theme menu cursor placement. * Fixed new attributes application during theme switching. * Assisted with debugging and testing. Marcin MichaÅ‚owski: * Added default and grey colours, and made first nice theme. Martin Milata: * Resolved Clang Static Analyzer warnings. * Dead code removal. * Miscellaneous code fixes. * Fix segfault when using -k command line option. * Added -q option to queue files from command line. * Provided play queue feature. * Fixed race condition between two clients and playlist request servicing. Jack Miller: * Added average bitrate field to the output of mocp -i. * Provided command line option to toggle/on/off playback options. * Provided command line option for setting the volume. * Added OnSongChange option which runs a command when song is changed. Alex Norman: * Added JACK output. * Assisted with reported JACK issues. * Added FastDir option. * Other improvements. Sebastian Parborg: * Silenced compiler warnings on various platforms. * Fixed bug attempting to read from unopened OSS mixer. * Assisted with testing. * Also assisted by commenting on feature ideas. Ted Phelps: * Fixed incorrect referencing of ALSA mixer channels. Petr Pisar: * Provided patch upon which the initial locale support was based. Jonathan Poelen: * Removed redundant call to file_type(). Alexander Polakov: * Fixed miscoloured frame when switching themes. * Provided SNDIO sound driver for OpenBSD systems. * Fixed call for bit rate after file open has failed. * Assisted with testing. Elimar Riesebieter: * Tested on PPC (made to work on big endian architectures). * Builder of the official Debian package. * Described --seek option in the manpage. * Added JACK to '-R' option sound drivers on manpage. Alexandrov Sergey: * Added Wavpack decoder. * Fixed 8-, 24- and 32-bit decoding in Wavpack decoder. Aleks Sherikov: * Added EnforceTagsEncoding, FileNamesIconv, and FileNamesIconv options. Joerg Sonnenberger: * Fixed using ncurses on NetBSD. * Fixed detecting curses if ncurses is not present. Wim Speekenbrink: * Author of nightly_theme. Daniel Stenberg: * Fixed CURL timeout so internet streaming errors don't hang MOC. OndÅ™ej Svoboda: * Fixed a fatal error when opening an MP3 file. * Fixed a compilation warning in the FFmpeg plugin. * Spelling fixes. * Source files encoding fixes. Kamil Tarkowski: * Provided 'back' command. * Some fixes and small improvements. Reuben Thomas: * Fixed typos in documentation. * Fixed and simplify parameters substitution in --format command. * Don't run the server if the user doesn't really want to do that when using few commands from command line like --info. * Reorganised code that parses command line options. * Allowed the use of FormatString tags in --format arguments. Richard Toohey: * Assisted with testing on OpenBSD. Antonio Trande: * Assisted with testing. * Also assisted by commenting on feature ideas. * Fedora's MOC package builder. Marc Tschiesche: * Provided highlighted playlist numbers and file times. Zhiming Wang: * Assisted with testing on OSX. Jenny Wong: * Provided minor memory corruption patch. * Introduced FFmpeg to Internet streams. Marien Zwart: * Assisted with testing. "cbass": * Fixed segfault when trying to play a file using FFmpeg. * Migrated to newer FFmpeg API. "exc": * Pointed to an ALSA optimisation with joined channels. "firejox": * Fixed screen upsets due to UTF-8 character handing. "fluxid": * Fixed incorrect setting for themes red channel value. "GenghisKhan": * Reported bugs and significantly helped debugging them. * Greatly assisted with debugging the ALSA stutter bug. "meh": * Provided code to prefer reading ID3 tags v2 over v1. "scorched": * Assisted with testing. "thotypous": * Provided code to allow use of the OSSv4 per-application mixer API. "tokapix": * Provided additional proving of the ALSA stutter bug fix. "tyranix": * Provided new command 'Y' to prune unreadable files from the playlist. "vectis": * Assisted with debugging the ALSA stutter bug. "zaphod": * Some strcpy() to strncpy() changes. There are many people who have contributed in various ways to the development of MOC. I hope I've listed all who deserve thanks, but if not then I apologise and you should remind me so I can include you. moc-2.6.0~svn-r3005/utf8.c0000664000076400000500000001773512705572721014206 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005,2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #ifdef HAVE_ICONV # include #endif #ifdef HAVE_NL_TYPES_H # include #endif #ifdef HAVE_LANGINFO_H # include #endif #if defined HAVE_NCURSESW_CURSES_H # include #elif defined HAVE_NCURSESW_H # include #elif defined HAVE_NCURSES_CURSES_H # include #elif defined HAVE_NCURSES_H # include #elif defined HAVE_CURSES_H # include #endif #include #include #include #include #include "common.h" #include "log.h" #include "options.h" #include "utf8.h" #include "rcc.h" static char *terminal_charset = NULL; static int using_utf8 = 0; static iconv_t iconv_desc = (iconv_t)(-1); static iconv_t files_iconv_desc = (iconv_t)(-1); static iconv_t xterm_iconv_desc = (iconv_t)(-1); /* Return a malloc()ed string converted using iconv(). * If for_file_name is not 0, use the conversion defined for file names. * For NULL returns NULL. */ char *iconv_str (const iconv_t desc, const char *str) { char buf[512]; #ifdef FREEBSD const char *inbuf; #else char *inbuf; #endif char *outbuf; char *str_copy; size_t inbytesleft, outbytesleft; char *converted; if (!str) return NULL; if (desc == (iconv_t)(-1)) return xstrdup (str); inbuf = str_copy = xstrdup (str); outbuf = buf; inbytesleft = strlen(inbuf); outbytesleft = sizeof(buf) - 1; iconv (desc, NULL, NULL, NULL, NULL); while (inbytesleft) { if (iconv(desc, &inbuf, &inbytesleft, &outbuf, &outbytesleft) == (size_t)(-1)) { if (errno == EILSEQ) { inbuf++; inbytesleft--; if (!--outbytesleft) { *outbuf = 0; break; } *(outbuf++) = '#'; } else if (errno == EINVAL) { *(outbuf++) = '#'; *outbuf = 0; break; } else if (errno == E2BIG) { outbuf[sizeof(buf)-1] = 0; break; } } } *outbuf = 0; converted = xstrdup (buf); free (str_copy); return converted; } char *files_iconv_str (const char *str) { return iconv_str (files_iconv_desc, str); } char *xterm_iconv_str (const char *str) { return iconv_str (xterm_iconv_desc, str); } int xwaddstr (WINDOW *win, const char *str) { int res; if (using_utf8) res = waddstr (win, str); else { char *lstr = iconv_str (iconv_desc, str); res = waddstr (win, lstr); free (lstr); } return res; } /* Convert multi-byte sequence to wide characters. Change invalid UTF-8 * sequences to '?'. 'dest' can be NULL as in mbstowcs(). * If 'invalid_char' is not NULL it will be set to 1 if an invalid character * appears in the string, otherwise 0. */ static size_t xmbstowcs (wchar_t *dest, const char *src, size_t len, int *invalid_char) { mbstate_t ps; size_t count = 0; assert (src != NULL); assert (!dest || len > 0); memset (&ps, 0, sizeof(ps)); if (dest) memset (dest, 0, len * sizeof(wchar_t)); if (invalid_char) *invalid_char = 0; while (src && (len || !dest)) { size_t res; res = mbsrtowcs (dest, &src, len, &ps); if (res != (size_t)-1) { count += res; src = NULL; } else { size_t converted; src++; if (dest) { converted = wcslen (dest); dest += converted; count += converted; len -= converted; if (len > 1) { *dest = L'?'; dest++; *dest = L'\0'; len--; } else *(dest - 1) = L'\0'; } else count++; memset (&ps, 0, sizeof(ps)); if (invalid_char) *invalid_char = 1; } } return count; } int xwaddnstr (WINDOW *win, const char *str, const int n) { int res, width, inv_char; wchar_t *ucs; char *mstr, *lstr; size_t size, num_chars; assert (n > 0); assert (str != NULL); mstr = iconv_str (iconv_desc, str); size = xmbstowcs (NULL, mstr, -1, NULL) + 1; ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size); xmbstowcs (ucs, mstr, size, &inv_char); width = wcswidth (ucs, WIDTH_MAX); if (width == -1) { size_t clidx; for (clidx = 0; clidx < size - 1; clidx++) { if (wcwidth (ucs[clidx]) == -1) ucs[clidx] = L'?'; } width = wcswidth (ucs, WIDTH_MAX); inv_char = 1; } if (width > n) { while (width > n) width -= wcwidth (ucs[--size]); ucs[size] = L'\0'; } num_chars = wcstombs (NULL, ucs, 0); lstr = (char *)xmalloc (num_chars + 1); if (inv_char) wcstombs (lstr, ucs, num_chars + 1); else snprintf (lstr, num_chars + 1, "%s", mstr); res = waddstr (win, lstr); free (ucs); free (lstr); free (mstr); return res; } int xmvwaddstr (WINDOW *win, const int y, const int x, const char *str) { int res; if (using_utf8) res = mvwaddstr (win, y, x, str); else { char *lstr = iconv_str (iconv_desc, str); res = mvwaddstr (win, y, x, lstr); free (lstr); } return res; } int xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str, const int n) { int res; if (using_utf8) res = mvwaddnstr (win, y, x, str, n); else { char *lstr = iconv_str (iconv_desc, str); res = mvwaddnstr (win, y, x, lstr, n); free (lstr); } return res; } int xwprintw (WINDOW *win, const char *fmt, ...) { va_list va; int res; char *buf; va_start (va, fmt); buf = format_msg_va (fmt, va); va_end (va); if (using_utf8) res = waddstr (win, buf); else { char *lstr = iconv_str (iconv_desc, buf); res = waddstr (win, lstr); free (lstr); } free (buf); return res; } static void iconv_cleanup () { if (iconv_desc != (iconv_t)(-1) && iconv_close(iconv_desc) == -1) log_errno ("iconv_close() failed", errno); } void utf8_init () { #ifdef HAVE_NL_LANGINFO_CODESET #ifdef HAVE_NL_LANGINFO terminal_charset = xstrdup (nl_langinfo(CODESET)); assert (terminal_charset != NULL); if (!strcmp(terminal_charset, "UTF-8")) { #ifdef HAVE_NCURSESW logit ("Using UTF8 output"); using_utf8 = 1; #else /* HAVE_NCURSESW */ terminal_charset = xstrdup ("US-ASCII"); logit ("Using US-ASCII conversion - compiled without libncursesw"); #endif /* HAVE_NCURSESW */ } else logit ("Terminal character set: %s", terminal_charset); #else /* HAVE_NL_LANGINFO */ terminal_charset = xstrdup ("US-ASCII"); logit ("Assuming US-ASCII terminal character set"); #endif /* HAVE_NL_LANGINFO */ #endif /* HAVE_NL_LANGINFO_CODESET */ if (!using_utf8 && terminal_charset) { iconv_desc = iconv_open (terminal_charset, "UTF-8"); if (iconv_desc == (iconv_t)(-1)) log_errno ("iconv_open() failed", errno); } if (options_get_bool ("FileNamesIconv")) files_iconv_desc = iconv_open ("UTF-8", ""); if (options_get_bool ("NonUTFXterm")) xterm_iconv_desc = iconv_open ("", "UTF-8"); } void utf8_cleanup () { if (terminal_charset) free (terminal_charset); iconv_cleanup (); } /* Return the number of columns the string occupies when displayed. */ size_t strwidth (const char *s) { wchar_t *ucs; size_t size; size_t width; assert (s != NULL); size = xmbstowcs (NULL, s, -1, NULL) + 1; ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size); xmbstowcs (ucs, s, size, NULL); width = wcswidth (ucs, WIDTH_MAX); free (ucs); return width; } /* Return a malloc()ed string containing the tail of 'str' up to a * maximum of 'len' characters (in columns occupied on the screen). */ char *xstrtail (const char *str, const int len) { wchar_t *ucs; wchar_t *ucs_tail; size_t size; int width; char *tail; assert (str != NULL); assert (len > 0); size = xmbstowcs(NULL, str, -1, NULL) + 1; ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size); xmbstowcs (ucs, str, size, NULL); ucs_tail = ucs; width = wcswidth (ucs, WIDTH_MAX); assert (width >= 0); while (width > len) width -= wcwidth (*ucs_tail++); size = wcstombs (NULL, ucs_tail, 0) + 1; tail = (char *)xmalloc (size); wcstombs (tail, ucs_tail, size); free (ucs); return tail; } moc-2.6.0~svn-r3005/TODO0000664000076400000500000001441713242163005013622 0ustar riesebiesrcThis file does not list all bugs which: - are known but rarely encountered, - are not visible to the user, - are programming errors detectable only with the aid of testing tools, - are benign, or - have been reported but not yet confirmed to be attributable to MOC. It also does not contain many "To Do" items as these are often quite fluid and can evolve rapidly over time. The MOC Maintainer does hold a master list containing bugs, feature requests, program change requests and future requirements, and once it is possible to generate this file from that list automatically it may be updated more frequently so it is current and comprehensive. Known Bugs: * Character set problems: - Do we assume filesystem in UTF-8? It's incorrect. - id3tags v2.3 with UTF-16 are not properly handled by libid3tag, taglib has no problems. Need to use libtag here? - Recognition of ID3 tags v1 is broken (example: small.mp3). [node/234] - Perhaps MOC can add support for the frame field_type to differentiate between ID3_FIELD_TYPE_LATIN1 and ID3_FIELD_TYPE_STRING. [node/234] - Some national characters in file and directory names don't get displayed correctly. * Program crashes: - I have a problem with MOC 2.4.0 on FreeBSD 6.1-PRERELEASE. When I build MOC with musepack (libmpcdec) support MOC always core dumps on exit. (This may have been fixed by other storage-related patches and will be removed in MOC 2.7 if no one has confirmed it still exists.) - Using DB_THREAD in combination with DB_CREATE and DB_PRIVATE seems to cause a segfault on OpenWRT. The circumvention is to configure MOC on OpenWRT with '--disable-cache' or set the MOC configuration option 'TagsCacheSize' to zero. * Valgrind-detected memory problems: - libasound: memory leaks - libdb: uninitialised parameter storage - libffmpeg: uninitialised storage in conditions and reads - libgme: uninitialised storage in conditions and reads - libmagic: uninitialised storage in reads - librcc: uninitialised storage in conditions - libspeex: uninitialised storage in conditions and reads - The problems above occur in libraries but it needs to be determined whether they somehow arise from MOC's misuse of them. - There are many data race and mutex handling problems (particularly around the time of file-to-file transitions), but these are not visible to the users and difficult to solve without compromising gapless playback. * Directory and playlists: - When sorting by file name (directories), put files beginning with a non-alphanumeric character at the top. - mocp -a playlist.pls should not sort added files. [node/240] - Client does not show changes when a file's tags are updated by another program. - Client continues to show file names after ReadTags is toggled when MOC is started with 'ShowTime=yes' and 'ReadTags=no'. * Decoder problems: - Seeking in some formats does not work in the FFmpeg decoder because of the limitations of the FFmpeg libraries (and seeking is disabled for them). FLV also has seeking issues and is disabled but will be enabled if SEEK_IN_DECODER is set in the FFmpeg decoder plug-in. (This may be parameterised in due course.) - Some decoders do not currently pass the MD5 sum or sample count checks. - The duration of some AAC audios is incorrectly reported. This problem arises from the limitations of the format, but MOC now makes much more accurate guesses. Transitional Code Removal: * Remove TagLib version 1.5 requirement warning. * Remove Musepack libmpc (instead of libmpcdec) requirement warning. * Remove librcc raised requirement warning. Pending 2.6 Changes: * Require at least TagLib version 1.5. * Require Musepack libmpc (instead of libmpcdec). * Require at least librcc version 0.2.10. Pending 2.7 Changes: * Remove FreeBSD program crash bug if no one has confirmed it still exists. * Remove 'ALSAStutterDefeat' start-up warning. Future Wishlist (items marked with $ are bigger changes requiring more time): * Move TODO to moc.daper.net/roadmap . * A string-valued variable "TitleFormatString", similar to "FormatString". I'd suggest all the same %-escapes, plus: %F -- expands to (the expansion of) the current FormatString if there is a song playing or paused, and empty string if stopped. %S -- expands to play/stop/pause. * Command line option to play from a specified file/first from the last added files. [node/286] * Multiple file selection to move many files at once up and down. [node/288] * Review strcat()/strcpy()/sprintf() usage, change to strncat()/strncpy()/snprintf() where possible. * More side menus: many directory/playlist views. * LADSPA/LV2 * Editing the title of an item on the playlist (for Internet streams). *$ Media library: tree-like menu for artists and albums. * Seek by a % value (also using keys for 10%, 20%, etc). * Ability to play from a playlist made of items found after searching. * JACK: intelligent behaviour when we get disconnected from the server - detect it, try connect and exit if it fails. * FastGo instead of FastDir: go to a directory, URL or a playlist. * Read tags for sndfile formats. *$ http://www.peercast.org/ * Crossfade. * Command to see all information about a file with all tags, also all information about an internet stream (from IceCast headers like icy-url, icy-pub). *$ Lyrics downloaded from the Internet. * lirc. * Don't use PATH_MAX. * Seek forward using the content of the output buffer. *$ Locales. *$ Song ratings or something like Q in XMMS. * Configurable sorting. * Add a key for switching sort modes. *$ Equalizer like in Beep Media Player *$ Make equal volume level for every song like in http://volnorm.sourceforge.net *$ Replaygain. * Seek to arbitrary position (by typing a number). *$ ESD. *$ CUE-sheet. * Command line option to delete the currently played item from the playlist. * Scripting. * Some options can be changed at run-time using a menu. * posix_fadvise() with POSIX_FADV_WILLNEED instead of the input buffer thread for files. * Recording played sound to a file. * Upgrade TiMidity decoder plug-in to TiMidity++. This would add support for soundfonts (.sf2) which are now more common. Maybe Never: * Report ignored server-specific options when the server is not being run. * Funny ASCII equalizer. * Mouse support. [node/743] moc-2.6.0~svn-r3005/alsa.c0000664000076400000500000004636313012007156014222 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ /* Based on aplay copyright (c) by Jaroslav Kysela */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #define DEBUG #define STRERROR_FN alsa_strerror #include "common.h" #include "server.h" #include "audio.h" #include "options.h" #include "log.h" #define BUFFER_MAX_USEC 300000 /* Check that ALSA's and MOC's byte/sample/frame conversions agree. */ #ifndef NDEBUG # define ALSA_CHECK(fn,val) \ do { \ long v = val; \ ssize_t ssz = snd_pcm_##fn (handle, 1); \ if (ssz < 0) \ debug ("CHECK: snd_pcm_%s() failed: %s", #fn, alsa_strerror (ssz)); \ else if (v != ssz) \ debug ("CHECK: snd_pcm_%s() = %zd (vs %ld)", #fn, ssz, v); \ } while (0) #else # define ALSA_CHECK(...) do {} while (0) #endif static snd_pcm_t *handle = NULL; static struct { unsigned int channels; unsigned int rate; snd_pcm_format_t format; } params = { 0, 0, SND_PCM_FORMAT_UNKNOWN }; static snd_pcm_uframes_t buffer_frames; static snd_pcm_uframes_t chunk_frames; static int chunk_bytes = -1; static char alsa_buf[512 * 1024]; static int alsa_buf_fill = 0; static int bytes_per_frame; static int bytes_per_sample; static snd_mixer_t *mixer_handle = NULL; static snd_mixer_elem_t *mixer_elem1 = NULL; static snd_mixer_elem_t *mixer_elem2 = NULL; static snd_mixer_elem_t *mixer_elem_curr = NULL; /* Percentage volume setting for first and second mixer. */ static int volume1 = -1; static int volume2 = -1; /* ALSA-provided error code to description function wrapper. */ static inline char *alsa_strerror (int errnum) { char *result; if (errnum < 0) errnum = -errnum; if (errnum < SND_ERROR_BEGIN) result = xstrerror (errnum); else result = xstrdup (snd_strerror (errnum)); return result; } /* Map ALSA's mask to MOC's format (and visa versa). */ static const struct { snd_pcm_format_t mask; long format; } format_masks[] = { {SND_PCM_FORMAT_S8, SFMT_S8}, {SND_PCM_FORMAT_U8, SFMT_U8}, {SND_PCM_FORMAT_S16, SFMT_S16}, {SND_PCM_FORMAT_U16, SFMT_U16}, {SND_PCM_FORMAT_S32, SFMT_S32}, {SND_PCM_FORMAT_U32, SFMT_U32} }; /* Given an ALSA mask, return a MOC format or zero if unknown. */ static inline long mask_to_format (const snd_pcm_format_mask_t *mask) { long result = 0; for (size_t ix = 0; ix < ARRAY_SIZE(format_masks); ix += 1) { if (snd_pcm_format_mask_test (mask, format_masks[ix].mask)) result |= format_masks[ix].format; } #if 0 if (snd_pcm_format_mask_test (mask, SND_PCM_FORMAT_S24)) result |= SFMT_S32; /* conversion needed */ #endif return result; } /* Given a MOC format, return an ALSA mask. * Return SND_PCM_FORMAT_UNKNOWN if unknown. */ static inline snd_pcm_format_t format_to_mask (long format) { snd_pcm_format_t result = SND_PCM_FORMAT_UNKNOWN; for (size_t ix = 0; ix < ARRAY_SIZE(format_masks); ix += 1) { if (format_masks[ix].format == format) { result = format_masks[ix].mask; break; } } return result; } #ifndef NDEBUG static void alsa_log_cb (const char *unused1 ATTR_UNUSED, int unused2 ATTR_UNUSED, const char *unused3 ATTR_UNUSED, int unused4 ATTR_UNUSED, const char *fmt, ...) { char *msg; va_list va; assert (fmt); va_start (va, fmt); msg = format_msg_va (fmt, va); va_end (va); logit ("ALSA said: %s", msg); free (msg); } #endif static snd_pcm_hw_params_t *alsa_open_device (const char *device) { int rc; snd_pcm_hw_params_t *result; assert (!handle); rc = snd_pcm_open (&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (rc < 0) { error_errno ("Can't open audio", rc); goto err1; } rc = snd_pcm_hw_params_malloc (&result); if (rc < 0) { error_errno ("Can't allocate hardware parameters structure", rc); goto err2; } rc = snd_pcm_hw_params_any (handle, result); if (rc < 0) { error_errno ("Can't initialize hardware parameters structure", rc); goto err3; } if (0) { err3: snd_pcm_hw_params_free (result); err2: snd_pcm_close (handle); err1: result = NULL; handle = NULL; } return result; } /* Fill caps with the device capabilities. Return 0 on error. */ static int fill_capabilities (struct output_driver_caps *caps) { int result = 0; snd_pcm_hw_params_t *hw_params; assert (!handle); hw_params = alsa_open_device (options_get_str ("ALSADevice")); if (!hw_params) return 0; do { int rc; unsigned int val; snd_pcm_format_mask_t *format_mask; rc = snd_pcm_hw_params_get_channels_min (hw_params, &val); if (rc < 0) { error_errno ("Can't get the minimum number of channels", rc); break; } caps->min_channels = val; rc = snd_pcm_hw_params_get_channels_max (hw_params, &val); if (rc < 0) { error_errno ("Can't get the maximum number of channels", rc); break; } caps->max_channels = val; rc = snd_pcm_format_mask_malloc (&format_mask); if (rc < 0) { error_errno ("Can't allocate format mask", rc); break; } snd_pcm_hw_params_get_format_mask (hw_params, format_mask); caps->formats = mask_to_format (format_mask) | SFMT_NE; snd_pcm_format_mask_free (format_mask); result = 1; } while (0); snd_pcm_hw_params_free (hw_params); snd_pcm_close (handle); handle = NULL; return result; } static void handle_mixer_events (snd_mixer_t *mixer_handle) { struct pollfd *fds = NULL; assert (mixer_handle); do { int rc, count; count = snd_mixer_poll_descriptors_count (mixer_handle); if (count < 0) { log_errno ("snd_mixer_poll_descriptors_count() failed", count); break; } fds = xcalloc (count, sizeof (struct pollfd)); rc = snd_mixer_poll_descriptors (mixer_handle, fds, count); if (rc < 0) { log_errno ("snd_mixer_poll_descriptors() failed", rc); break; } rc = poll (fds, count, 0); if (rc < 0) { error_errno ("poll() failed", errno); break; } if (rc == 0) break; debug ("Mixer event"); rc = snd_mixer_handle_events (mixer_handle); if (rc < 0) log_errno ("snd_mixer_handle_events() failed", rc); } while (0); free (fds); } static int alsa_read_mixer_raw (snd_mixer_elem_t *elem) { int rc, nchannels = 0, volume = 0; bool joined; snd_mixer_selem_channel_id_t chan_id; if (!mixer_handle) return -1; assert (elem); handle_mixer_events (mixer_handle); joined = snd_mixer_selem_has_playback_volume_joined (elem); for (chan_id = 0; chan_id < SND_MIXER_SCHN_LAST; chan_id += 1) { if (snd_mixer_selem_has_playback_channel (elem, chan_id)) { long vol; nchannels += 1; rc = snd_mixer_selem_get_playback_volume (elem, chan_id, &vol); if (rc < 0) { error_errno ("Can't read mixer", rc); return -1; } assert (RANGE(0, vol, 100)); #if 0 { static int prev_vol[SND_MIXER_SCHN_LAST] = {0}; if (vol != prev_vol[chan_id]) { prev_vol[chan_id] = vol; debug ("Vol %d: %ld", chan_id, vol); } } #endif volume += vol; } if (joined) break; } if (nchannels == 0) { logit ("Mixer has no channels"); return -1; } volume /= nchannels; return volume; } static snd_mixer_elem_t *alsa_init_mixer_channel (const char *name) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *result = NULL; assert (mixer_handle); snd_mixer_selem_id_malloc (&sid); snd_mixer_selem_id_set_index (sid, 0); snd_mixer_selem_id_set_name (sid, name); do { snd_mixer_elem_t *elem = NULL; elem = snd_mixer_find_selem (mixer_handle, sid); if (!elem) { error ("Can't find mixer %s", name); break; } if (!snd_mixer_selem_has_playback_volume (elem)) { error ("Mixer device has no playback volume (%s).", name); break; } if (snd_mixer_selem_set_playback_volume_range (elem, 0, 100) < 0) { error ("Cannot set playback volume range (%s).", name); break; } logit ("Opened mixer (%s)", name); result = elem; } while (0); snd_mixer_selem_id_free (sid); return result; } static void alsa_close_mixer () { if (mixer_handle) { int rc; rc = snd_mixer_close (mixer_handle); if (rc < 0) log_errno ("Can't close mixer", rc); mixer_handle = NULL; } } static void alsa_open_mixer (const char *device) { int rc; assert (!mixer_handle); rc = snd_mixer_open (&mixer_handle, 0); if (rc < 0) { error_errno ("Can't open ALSA mixer", rc); goto err; } rc = snd_mixer_attach (mixer_handle, device); if (rc < 0) { error_errno ("Can't attach mixer", rc); goto err; } rc = snd_mixer_selem_register (mixer_handle, NULL, NULL); if (rc < 0) { error_errno ("Can't register mixer", rc); goto err; } rc = snd_mixer_load (mixer_handle); if (rc < 0) { error_errno ("Can't load mixer", rc); goto err; } if (0) { err: alsa_close_mixer (); } } static void alsa_set_current_mixer () { int vol; if (mixer_elem1 && (vol = alsa_read_mixer_raw (mixer_elem1)) != -1) { assert (RANGE(0, vol, 100)); volume1 = vol; } else { mixer_elem1 = NULL; mixer_elem_curr = mixer_elem2; } if (mixer_elem2 && (vol = alsa_read_mixer_raw (mixer_elem2)) != -1) { assert (RANGE(0, vol, 100)); volume2 = vol; } else { mixer_elem2 = NULL; mixer_elem_curr = mixer_elem1; } } static void alsa_shutdown () { alsa_close_mixer (); #ifndef NDEBUG snd_lib_error_set_handler (NULL); #endif } static int alsa_init (struct output_driver_caps *caps) { int result = 0; const char *device; assert (!mixer_handle); device = options_get_str ("ALSADevice"); logit ("Initialising ALSA device: %s", device); #ifndef NDEBUG snd_lib_error_set_handler (alsa_log_cb); #endif alsa_open_mixer (device); if (mixer_handle) { mixer_elem1 = alsa_init_mixer_channel (options_get_str ("ALSAMixer1")); mixer_elem2 = alsa_init_mixer_channel (options_get_str ("ALSAMixer2")); } mixer_elem_curr = mixer_elem1 ? mixer_elem1 : mixer_elem2; if (mixer_elem_curr) alsa_set_current_mixer (); if (!mixer_elem_curr) goto err; result = fill_capabilities (caps); if (result == 0) goto err; if (sizeof (long) < 8 && options_was_defaulted ("ALSAStutterDefeat")) { fprintf (stderr, "\n" "Warning: Your system may be vulnerable to stuttering audio.\n" " You should read the example configuration file comments\n" " for the 'ALSAStutterDefeat' option and set it accordingly.\n" " Setting the option will remove this warning.\n" "\n"); xsleep (5, 1); } if (0) { err: alsa_shutdown (); } return result; } static int alsa_open (struct sound_params *sound_params) { int rc, result = 0; unsigned int period_time, buffer_time; char fmt_name[128]; const char *device; snd_pcm_hw_params_t *hw_params; assert (!handle); params.format = format_to_mask (sound_params->fmt & SFMT_MASK_FORMAT); if (params.format == SND_PCM_FORMAT_UNKNOWN) { error ("Unknown sample format: %s", sfmt_str (sound_params->fmt, fmt_name, sizeof (fmt_name))); return 0; } device = options_get_str ("ALSADevice"); logit ("Opening ALSA device: %s", device); hw_params = alsa_open_device (device); if (!hw_params) return 0; rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (rc < 0) { error_errno ("Can't set ALSA access type", rc); goto err; } rc = snd_pcm_hw_params_set_format (handle, hw_params, params.format); if (rc < 0) { error_errno ("Can't set sample format", rc); goto err; } bytes_per_sample = sfmt_Bps (sound_params->fmt); logit ("Set sample width: %d bytes", bytes_per_sample); if (options_get_bool ("ALSAStutterDefeat")) { rc = snd_pcm_hw_params_set_rate_resample (handle, hw_params, 0); if (rc == 0) logit ("ALSA resampling disabled"); else log_errno ("Unable to disable ALSA resampling", rc); } params.rate = sound_params->rate; rc = snd_pcm_hw_params_set_rate_near (handle, hw_params, ¶ms.rate, 0); if (rc < 0) { error_errno ("Can't set sample rate", rc); goto err; } logit ("Set rate: %uHz", params.rate); rc = snd_pcm_hw_params_set_channels (handle, hw_params, sound_params->channels); if (rc < 0) { error_errno ("Can't set number of channels", rc); goto err; } logit ("Set channels: %d", sound_params->channels); rc = snd_pcm_hw_params_get_buffer_time_max (hw_params, &buffer_time, 0); if (rc < 0) { error_errno ("Can't get maximum buffer time", rc); goto err; } buffer_time = MIN(buffer_time, BUFFER_MAX_USEC); period_time = buffer_time / 4; rc = snd_pcm_hw_params_set_period_time_near (handle, hw_params, &period_time, 0); if (rc < 0) { error_errno ("Can't set period time", rc); goto err; } rc = snd_pcm_hw_params_set_buffer_time_near (handle, hw_params, &buffer_time, 0); if (rc < 0) { error_errno ("Can't set buffer time", rc); goto err; } rc = snd_pcm_hw_params (handle, hw_params); if (rc < 0) { error_errno ("Can't set audio parameters", rc); goto err; } snd_pcm_hw_params_get_period_size (hw_params, &chunk_frames, 0); debug ("Chunk size: %lu frames", chunk_frames); snd_pcm_hw_params_get_buffer_size (hw_params, &buffer_frames); debug ("Buffer size: %lu frames", buffer_frames); debug ("Buffer time: %"PRIu64"us", (uint64_t) buffer_frames * UINT64_C(1000000) / params.rate); bytes_per_frame = sound_params->channels * bytes_per_sample; debug ("Frame size: %d bytes", bytes_per_frame); chunk_bytes = chunk_frames * bytes_per_frame; if (chunk_frames == buffer_frames) { error ("Can't use period equal to buffer size (%lu == %lu)", chunk_frames, buffer_frames); goto err; } rc = snd_pcm_prepare (handle); if (rc < 0) { error_errno ("Can't prepare audio interface for use", rc); goto err; } ALSA_CHECK (samples_to_bytes, bytes_per_sample); ALSA_CHECK (frames_to_bytes, bytes_per_frame); logit ("ALSA device opened"); params.channels = sound_params->channels; alsa_buf_fill = 0; result = 1; err: snd_pcm_hw_params_free (hw_params); return result; } /* Play from alsa_buf as many chunks as possible. Move the remaining data * to the beginning of the buffer. Return the number of bytes written * or -1 on error. */ static int play_buf_chunks () { int written = 0; bool zero_logged = false; while (alsa_buf_fill >= chunk_bytes) { int rc; rc = snd_pcm_writei (handle, alsa_buf + written, chunk_frames); if (rc == 0) { if (!zero_logged) { debug ("Played 0 bytes"); zero_logged = true; } continue; } zero_logged = false; if (rc > 0) { int written_bytes = rc * bytes_per_frame; written += written_bytes; alsa_buf_fill -= written_bytes; debug ("Played %d bytes", written_bytes); continue; } rc = snd_pcm_recover (handle, rc, 0); switch (rc) { case 0: break; case -EAGAIN: if (snd_pcm_wait (handle, 500) < 0) logit ("snd_pcm_wait() failed"); break; default: error_errno ("Can't play", rc); return -1; } } debug ("%d bytes remain in alsa_buf", alsa_buf_fill); memmove (alsa_buf, alsa_buf + written, alsa_buf_fill); return written; } static void alsa_close () { snd_pcm_sframes_t delay; assert (handle != NULL); /* play what remained in the buffer */ if (alsa_buf_fill > 0) { unsigned int samples_required; assert (alsa_buf_fill < chunk_bytes); samples_required = (chunk_bytes - alsa_buf_fill) / bytes_per_sample; snd_pcm_format_set_silence (params.format, alsa_buf + alsa_buf_fill, samples_required); alsa_buf_fill = chunk_bytes; play_buf_chunks (); } /* Wait for ALSA buffers to empty. * Do not be tempted to use snd_pcm_nonblock() and snd_pcm_drain() * here; there are two bugs in ALSA which make it a bad idea (see * the SVN commit log for r2550). Instead we sleep for the duration * of the still unplayed samples. */ if (snd_pcm_delay (handle, &delay) == 0 && delay > 0) xsleep (delay, params.rate); snd_pcm_close (handle); logit ("ALSA device closed"); params.format = 0; params.rate = 0; params.channels = 0; buffer_frames = 0; chunk_frames = 0; chunk_bytes = -1; handle = NULL; } static int alsa_play (const char *buff, const size_t size) { int to_write = size; int buf_pos = 0; assert (chunk_bytes > 0); debug ("Got %zu bytes to play", size); while (to_write) { int to_copy; to_copy = MIN(to_write, ssizeof(alsa_buf) - alsa_buf_fill); memcpy (alsa_buf + alsa_buf_fill, buff + buf_pos, to_copy); to_write -= to_copy; buf_pos += to_copy; alsa_buf_fill += to_copy; debug ("Copied %d bytes to alsa_buf (now filled with %d bytes)", to_copy, alsa_buf_fill); if (play_buf_chunks() < 0) return -1; } debug ("Played everything"); return size; } static int alsa_read_mixer () { int actual_vol, *vol; actual_vol = alsa_read_mixer_raw (mixer_elem_curr); assert (RANGE(0, actual_vol, 100)); if (mixer_elem_curr == mixer_elem1) vol = &volume1; else vol = &volume2; if (*vol != actual_vol) { *vol = actual_vol; logit ("Mixer volume has changed since we last read it."); } return actual_vol; } static void alsa_set_mixer (int vol) { int rc; assert (RANGE(0, vol, 100)); if (!mixer_handle) return; if (mixer_elem_curr == mixer_elem1) volume1 = vol; else volume2 = vol; debug ("Setting vol to %d", vol); rc = snd_mixer_selem_set_playback_volume_all (mixer_elem_curr, vol); if (rc < 0) error_errno ("Can't set mixer", rc); } static int alsa_get_buff_fill () { int result = 0; do { int rc; snd_pcm_sframes_t delay; if (!handle) break; rc = snd_pcm_delay (handle, &delay); if (rc < 0) { log_errno ("snd_pcm_delay() failed", rc); break; } /* delay can be negative if an underrun occurs */ result = MAX(delay, 0) * bytes_per_frame; } while (0); return result; } static int alsa_reset () { int result = 0; do { int rc; if (!handle) { logit ("alsa_reset() when the device is not opened."); break; } rc = snd_pcm_drop (handle); if (rc < 0) { error_errno ("Can't reset the device", rc); break; } rc = snd_pcm_prepare (handle); if (rc < 0) { error_errno ("Can't prepare after reset", rc); break; } alsa_buf_fill = 0; result = 1; } while (0); return result; } static int alsa_get_rate () { return params.rate; } static void alsa_toggle_mixer_channel () { if (mixer_elem_curr == mixer_elem1 && mixer_elem2) mixer_elem_curr = mixer_elem2; else if (mixer_elem1) mixer_elem_curr = mixer_elem1; } static char *alsa_get_mixer_channel_name () { char *result; if (mixer_elem_curr == mixer_elem1) result = xstrdup (options_get_str ("ALSAMixer1")); else result = xstrdup (options_get_str ("ALSAMixer2")); return result; } void alsa_funcs (struct hw_funcs *funcs) { funcs->init = alsa_init; funcs->shutdown = alsa_shutdown; funcs->open = alsa_open; funcs->close = alsa_close; funcs->play = alsa_play; funcs->read_mixer = alsa_read_mixer; funcs->set_mixer = alsa_set_mixer; funcs->get_buff_fill = alsa_get_buff_fill; funcs->reset = alsa_reset; funcs->get_rate = alsa_get_rate; funcs->toggle_mixer_channel = alsa_toggle_mixer_channel; funcs->get_mixer_channel_name = alsa_get_mixer_channel_name; } moc-2.6.0~svn-r3005/alsa.h0000664000076400000500000000022611322047704014220 0ustar riesebiesrc#ifndef ALSA_H #define ALSA_H #ifdef __cplusplus extern "C" { #endif void alsa_funcs (struct hw_funcs *funcs); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/audio.c0000664000076400000500000006744313012456773014422 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004-2006 Damian Pietras * * 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. * * Contributors: * - Kamil Tarkowski - "previous" request * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #define DEBUG #include "common.h" #include "server.h" #include "decoder.h" #include "playlist.h" #include "log.h" #include "lists.h" #ifdef HAVE_OSS # include "oss.h" #endif #ifdef HAVE_SNDIO # include "sndio_out.h" #endif #ifdef HAVE_ALSA # include "alsa.h" #endif #ifndef NDEBUG # include "null_out.h" #endif #ifdef HAVE_JACK # include "jack.h" #endif #include "softmixer.h" #include "equalizer.h" #include "out_buf.h" #include "protocol.h" #include "options.h" #include "player.h" #include "audio.h" #include "files.h" #include "io.h" #include "audio_conversion.h" static pthread_t playing_thread = 0; /* tid of play thread */ static int play_thread_running = 0; /* currently played file */ static int curr_playing = -1; /* file we played before playing songs from queue */ static char *before_queue_fname = NULL; static char *curr_playing_fname = NULL; /* This flag is set 1 if audio_play() was called with nonempty queue, * so we know that when the queue is empty, we should play the regular * playlist from the beginning. */ static int started_playing_in_queue = 0; static pthread_mutex_t curr_playing_mtx = PTHREAD_MUTEX_INITIALIZER; static struct out_buf *out_buf; static struct hw_funcs hw; static struct output_driver_caps hw_caps; /* capabilities of the output driver */ /* Player state. */ static int state = STATE_STOP; static int prev_state = STATE_STOP; /* requests for playing thread */ static int stop_playing = 0; static int play_next = 0; static int play_prev = 0; static pthread_mutex_t request_mtx = PTHREAD_MUTEX_INITIALIZER; /* Playlists. */ static struct plist playlist; static struct plist shuffled_plist; static struct plist queue; static struct plist *curr_plist; /* currently used playlist */ static pthread_mutex_t plist_mtx = PTHREAD_MUTEX_INITIALIZER; /* Is the audio device opened? */ static int audio_opened = 0; /* Current sound parameters (with which the device is opened). */ static struct sound_params driver_sound_params = { 0, 0, 0 }; /* Sound parameters requested by the decoder. */ static struct sound_params req_sound_params = { 0, 0, 0 }; static struct audio_conversion sound_conv; static int need_audio_conversion = 0; /* URL of the last played stream. Used to fake pause/unpause of internet * streams. Protected by curr_playing_mtx. */ static char *last_stream_url = NULL; static int current_mixer = 0; /* Check if the two sample rates don't differ so much that we can't play. */ #define sample_rate_compat(sound, device) ((device) * 1.05 >= sound \ && (device) * 0.95 <= sound) /* Make a human readable description of the sound sample format(s). * Put the description in msg which is of size buf_size. * Return msg. */ char *sfmt_str (const long format, char *msg, const size_t buf_size) { assert (sound_format_ok(format)); assert (buf_size > 0); msg[0] = 0; if (format & SFMT_S8) strncat (msg, ", 8-bit signed", buf_size - strlen(msg) - 1); if (format & SFMT_U8) strncat (msg, ", 8-bit unsigned", buf_size - strlen(msg) - 1); if (format & SFMT_S16) strncat (msg, ", 16-bit signed", buf_size - strlen(msg) - 1); if (format & SFMT_U16) strncat (msg, ", 16-bit unsigned", buf_size - strlen(msg) - 1); if (format & SFMT_S32) strncat (msg, ", 24-bit signed (as 32-bit samples)", buf_size - strlen(msg) - 1); if (format & SFMT_U32) strncat (msg, ", 24-bit unsigned (as 32-bit samples)", buf_size - strlen(msg) - 1); if (format & SFMT_FLOAT) strncat (msg, ", float", buf_size - strlen(msg) - 1); if (format & SFMT_LE) strncat (msg, " little-endian", buf_size - strlen(msg) - 1); else if (format & SFMT_BE) strncat (msg, " big-endian", buf_size - strlen(msg) - 1); if (format & SFMT_NE) strncat (msg, " (native)", buf_size - strlen(msg) - 1); /* skip first ", " */ if (msg[0]) memmove (msg, msg + 2, strlen(msg) + 1); return msg; } /* Return != 0 if fmt1 and fmt2 have the same sample width. */ int sfmt_same_bps (const long fmt1, const long fmt2) { if (fmt1 & (SFMT_S8 | SFMT_U8) && fmt2 & (SFMT_S8 | SFMT_U8)) return 1; if (fmt1 & (SFMT_S16 | SFMT_U16) && fmt2 & (SFMT_S16 | SFMT_U16)) return 1; if (fmt1 & (SFMT_S32 | SFMT_U32) && fmt2 & (SFMT_S32 | SFMT_U32)) return 1; if (fmt1 & fmt2 & SFMT_FLOAT) return 1; return 0; } /* Return the best matching sample format for the requested format and * available format mask. */ static long sfmt_best_matching (const long formats_with_endian, const long req_with_endian) { long formats = formats_with_endian & SFMT_MASK_FORMAT; long req = req_with_endian & SFMT_MASK_FORMAT; long best = 0; char fmt_name1[SFMT_STR_MAX] DEBUG_ONLY; char fmt_name2[SFMT_STR_MAX] DEBUG_ONLY; if (formats & req) best = req; else if (req == SFMT_S8 || req == SFMT_U8) { if (formats & SFMT_S8) best = SFMT_S8; else if (formats & SFMT_U8) best = SFMT_U8; else if (formats & SFMT_S16) best = SFMT_S16; else if (formats & SFMT_U16) best = SFMT_U16; else if (formats & SFMT_S32) best = SFMT_S32; else if (formats & SFMT_U32) best = SFMT_U32; else if (formats & SFMT_FLOAT) best = SFMT_FLOAT; } else if (req == SFMT_S16 || req == SFMT_U16) { if (formats & SFMT_S16) best = SFMT_S16; else if (formats & SFMT_U16) best = SFMT_U16; else if (formats & SFMT_S32) best = SFMT_S32; else if (formats & SFMT_U32) best = SFMT_U32; else if (formats & SFMT_FLOAT) best = SFMT_FLOAT; else if (formats & SFMT_S8) best = SFMT_S8; else if (formats & SFMT_U8) best = SFMT_U8; } else if (req == SFMT_S32 || req == SFMT_U32 || req == SFMT_FLOAT) { if (formats & SFMT_S32) best = SFMT_S32; else if (formats & SFMT_U32) best = SFMT_U32; else if (formats & SFMT_S16) best = SFMT_S16; else if (formats & SFMT_U16) best = SFMT_U16; else if (formats & SFMT_FLOAT) best = SFMT_FLOAT; else if (formats & SFMT_S8) best = SFMT_S8; else if (formats & SFMT_U8) best = SFMT_U8; } assert (best != 0); if (!(best & (SFMT_S8 | SFMT_U8))) { if ((formats_with_endian & SFMT_LE) && (formats_with_endian & SFMT_BE)) best |= SFMT_NE; else best |= formats_with_endian & SFMT_MASK_ENDIANNESS; } debug ("Chose %s as the best matching %s", sfmt_str(best, fmt_name1, sizeof(fmt_name1)), sfmt_str(req_with_endian, fmt_name2, sizeof(fmt_name2))); return best; } /* Return the number of bytes per sample for the given format. */ int sfmt_Bps (const long format) { int Bps = -1; switch (format & SFMT_MASK_FORMAT) { case SFMT_S8: case SFMT_U8: Bps = 1; break; case SFMT_S16: case SFMT_U16: Bps = 2; break; case SFMT_S32: case SFMT_U32: Bps = 4; break; case SFMT_FLOAT: Bps = sizeof (float); break; } assert (Bps > 0); return Bps; } /* Move to the next file depending on the options set, the user * request and whether or not there are files in the queue. */ static void go_to_another_file () { bool shuffle = options_get_bool ("Shuffle"); bool go_next = (play_next || options_get_bool("AutoNext")); int curr_playing_curr_pos; /* XXX: Shouldn't play_next be protected by mutex? */ LOCK (curr_playing_mtx); LOCK (plist_mtx); /* If we move forward in the playlist and there are some songs in * the queue, then play them. */ if (plist_count(&queue) && go_next) { logit ("Playing file from queue"); if (!before_queue_fname && curr_playing_fname) before_queue_fname = xstrdup (curr_playing_fname); curr_plist = &queue; curr_playing = plist_next (&queue, -1); server_queue_pop (queue.items[curr_playing].file); plist_delete (&queue, curr_playing); } else { /* If we just finished playing files from the queue and the * appropriate option is set, continue with the file played * before playing the queue. */ if (before_queue_fname && options_get_bool ("QueueNextSongReturn")) { free (curr_playing_fname); curr_playing_fname = before_queue_fname; before_queue_fname = NULL; } if (shuffle) { curr_plist = &shuffled_plist; if (plist_count(&playlist) && !plist_count(&shuffled_plist)) { plist_cat (&shuffled_plist, &playlist); plist_shuffle (&shuffled_plist); if (curr_playing_fname) plist_swap_first_fname (&shuffled_plist, curr_playing_fname); } } else curr_plist = &playlist; curr_playing_curr_pos = plist_find_fname (curr_plist, curr_playing_fname); /* If we came from the queue and the last file in * queue wasn't in the playlist, we try to revert to * the QueueNextSongReturn == true behaviour. */ if (curr_playing_curr_pos == -1 && before_queue_fname) { curr_playing_curr_pos = plist_find_fname (curr_plist, before_queue_fname); } if (play_prev && plist_count(curr_plist)) { logit ("Playing previous..."); if (curr_playing_curr_pos == -1 || started_playing_in_queue) { curr_playing = plist_prev (curr_plist, -1); started_playing_in_queue = 0; } else curr_playing = plist_prev (curr_plist, curr_playing_curr_pos); if (curr_playing == -1) { if (options_get_bool("Repeat")) curr_playing = plist_last (curr_plist); logit ("Beginning of the list."); } else logit ("Previous item."); } else if (go_next && plist_count(curr_plist)) { logit ("Playing next..."); if (curr_playing_curr_pos == -1 || started_playing_in_queue) { curr_playing = plist_next (curr_plist, -1); started_playing_in_queue = 0; } else curr_playing = plist_next (curr_plist, curr_playing_curr_pos); if (curr_playing == -1 && options_get_bool("Repeat")) { if (shuffle) { plist_clear (&shuffled_plist); plist_cat (&shuffled_plist, &playlist); plist_shuffle (&shuffled_plist); } curr_playing = plist_next (curr_plist, -1); logit ("Going back to the first item."); } else if (curr_playing == -1) logit ("End of the list"); else logit ("Next item"); } else if (!options_get_bool("Repeat")) { curr_playing = -1; } else debug ("Repeating file"); if (before_queue_fname) free (before_queue_fname); before_queue_fname = NULL; } UNLOCK (plist_mtx); UNLOCK (curr_playing_mtx); } static void *play_thread (void *unused ATTR_UNUSED) { logit ("Entering playing thread"); while (curr_playing != -1) { char *file; LOCK (plist_mtx); file = plist_get_file (curr_plist, curr_playing); UNLOCK (plist_mtx); play_next = 0; play_prev = 0; if (file) { int next; char *next_file; LOCK (curr_playing_mtx); LOCK (plist_mtx); logit ("Playing item %d: %s", curr_playing, file); if (curr_playing_fname) free (curr_playing_fname); curr_playing_fname = xstrdup (file); out_buf_time_set (out_buf, 0.0); next = plist_next (curr_plist, curr_playing); next_file = next != -1 ? plist_get_file (curr_plist, next) : NULL; UNLOCK (plist_mtx); UNLOCK (curr_playing_mtx); player (file, next_file, out_buf); if (next_file) free (next_file); set_info_rate (0); set_info_bitrate (0); set_info_channels (1); out_buf_time_set (out_buf, 0.0); free (file); } LOCK (curr_playing_mtx); if (last_stream_url) { free (last_stream_url); last_stream_url = NULL; } UNLOCK (curr_playing_mtx); if (stop_playing) { LOCK (curr_playing_mtx); curr_playing = -1; UNLOCK (curr_playing_mtx); logit ("stopped"); } else go_to_another_file (); } prev_state = state; state = STATE_STOP; state_change (); if (curr_playing_fname) { free (curr_playing_fname); curr_playing_fname = NULL; } audio_close (); logit ("Exiting"); return NULL; } void audio_reset () { if (hw.reset) hw.reset (); } void audio_stop () { int rc; if (play_thread_running) { logit ("audio_stop()"); LOCK (request_mtx); stop_playing = 1; UNLOCK (request_mtx); player_stop (); logit ("pthread_join (playing_thread, NULL)"); rc = pthread_join (playing_thread, NULL); if (rc != 0) log_errno ("pthread_join() failed", rc); playing_thread = 0; play_thread_running = 0; stop_playing = 0; logit ("done stopping"); } else if (state == STATE_PAUSE) { /* Paused internet stream - we are in fact stopped already. */ if (curr_playing_fname) { free (curr_playing_fname); curr_playing_fname = NULL; } prev_state = state; state = STATE_STOP; state_change (); } } /* Start playing from the file fname. If fname is an empty string, * start playing from the first file on the list. */ void audio_play (const char *fname) { int rc; audio_stop (); player_reset (); LOCK (curr_playing_mtx); LOCK (plist_mtx); /* If we have songs in the queue and fname is empty string, start * playing file from the queue. */ if (plist_count(&queue) && !(*fname)) { curr_plist = &queue; curr_playing = plist_next (&queue, -1); /* remove the file from queue */ server_queue_pop (queue.items[curr_playing].file); plist_delete (curr_plist, curr_playing); started_playing_in_queue = 1; } else if (options_get_bool("Shuffle")) { plist_clear (&shuffled_plist); plist_cat (&shuffled_plist, &playlist); plist_shuffle (&shuffled_plist); plist_swap_first_fname (&shuffled_plist, fname); curr_plist = &shuffled_plist; if (*fname) curr_playing = plist_find_fname (curr_plist, fname); else if (plist_count(curr_plist)) { curr_playing = plist_next (curr_plist, -1); } else curr_playing = -1; } else { curr_plist = &playlist; if (*fname) curr_playing = plist_find_fname (curr_plist, fname); else if (plist_count(curr_plist)) curr_playing = plist_next (curr_plist, -1); else curr_playing = -1; } rc = pthread_create (&playing_thread, NULL, play_thread, NULL); if (rc != 0) error_errno ("Can't create thread", rc); play_thread_running = 1; UNLOCK (plist_mtx); UNLOCK (curr_playing_mtx); } void audio_next () { if (play_thread_running) { play_next = 1; player_stop (); } } void audio_prev () { if (play_thread_running) { play_prev = 1; player_stop (); } } void audio_pause () { LOCK (curr_playing_mtx); LOCK (plist_mtx); if (curr_playing != -1) { char *sname = plist_get_file (curr_plist, curr_playing); if (file_type(sname) == F_URL) { UNLOCK (curr_playing_mtx); UNLOCK (plist_mtx); audio_stop (); LOCK (curr_playing_mtx); LOCK (plist_mtx); if (last_stream_url) free (last_stream_url); last_stream_url = xstrdup (sname); /* Pretend that we are paused on this. */ curr_playing_fname = xstrdup (sname); } else out_buf_pause (out_buf); prev_state = state; state = STATE_PAUSE; state_change (); free (sname); } UNLOCK (plist_mtx); UNLOCK (curr_playing_mtx); } void audio_unpause () { LOCK (curr_playing_mtx); if (last_stream_url && file_type(last_stream_url) == F_URL) { char *url = xstrdup (last_stream_url); UNLOCK (curr_playing_mtx); audio_play (url); free (url); } else if (curr_playing != -1) { out_buf_unpause (out_buf); prev_state = state; state = STATE_PLAY; UNLOCK (curr_playing_mtx); state_change (); } else UNLOCK (curr_playing_mtx); } static void reset_sound_params (struct sound_params *params) { params->rate = 0; params->channels = 0; params->fmt = 0; } /* Return 0 on error. If sound params == NULL, open the device using * the previous parameters. */ int audio_open (struct sound_params *sound_params) { int res; static struct sound_params last_params = { 0, 0, 0 }; if (!sound_params) sound_params = &last_params; else last_params = *sound_params; assert (sound_format_ok(sound_params->fmt)); if (audio_opened) { if (sound_params_eq(req_sound_params, *sound_params)) { if (audio_get_bps() >= 88200) { logit ("Audio device already opened with such parameters."); return 1; } /* Not closing the device would cause that much * sound from the previous file to stay in the buffer * and the user will hear old data, so close it. */ logit ("Reopening device due to low bps."); } audio_close (); } req_sound_params = *sound_params; /* Set driver_sound_params to parameters supported by the driver that * are nearly the requested parameters. */ if (options_get_int("ForceSampleRate")) { driver_sound_params.rate = options_get_int("ForceSampleRate"); logit ("Setting forced driver sample rate to %dHz", driver_sound_params.rate); } else driver_sound_params.rate = req_sound_params.rate; driver_sound_params.fmt = sfmt_best_matching (hw_caps.formats, req_sound_params.fmt); /* number of channels */ driver_sound_params.channels = CLAMP(hw_caps.min_channels, req_sound_params.channels, hw_caps.max_channels); res = hw.open (&driver_sound_params); if (res) { char fmt_name[SFMT_STR_MAX] LOGIT_ONLY; driver_sound_params.rate = hw.get_rate (); if (driver_sound_params.fmt != req_sound_params.fmt || driver_sound_params.channels != req_sound_params.channels || (!sample_rate_compat( req_sound_params.rate, driver_sound_params.rate))) { logit ("Conversion of the sound is needed."); if (!audio_conv_new (&sound_conv, &req_sound_params, &driver_sound_params)) { hw.close (); reset_sound_params (&req_sound_params); return 0; } need_audio_conversion = 1; } audio_opened = 1; logit ("Requested sound parameters: %s, %d channels, %dHz", sfmt_str(req_sound_params.fmt, fmt_name, sizeof(fmt_name)), req_sound_params.channels, req_sound_params.rate); logit ("Driver sound parameters: %s, %d channels, %dHz", sfmt_str(driver_sound_params.fmt, fmt_name, sizeof(fmt_name)), driver_sound_params.channels, driver_sound_params.rate); } return res; } int audio_send_buf (const char *buf, const size_t size) { size_t out_data_len = size; int res; char *converted = NULL; if (need_audio_conversion) converted = audio_conv (&sound_conv, buf, size, &out_data_len); if (need_audio_conversion && converted) res = out_buf_put (out_buf, converted, out_data_len); else if (!need_audio_conversion) res = out_buf_put (out_buf, buf, size); else res = 0; if (converted) free (converted); return res; } /* Get the current audio format bytes per frame value. * May return 0 if the audio device is closed. */ int audio_get_bpf () { return driver_sound_params.channels * (driver_sound_params.fmt ? sfmt_Bps(driver_sound_params.fmt) : 0); } /* Get the current audio format bytes per second value. * May return 0 if the audio device is closed. */ int audio_get_bps () { return driver_sound_params.rate * audio_get_bpf (); } int audio_get_buf_fill () { return hw.get_buff_fill (); } int audio_send_pcm (const char *buf, const size_t size) { char *softmixed = NULL; char *equalized = NULL; if (equalizer_is_active ()) { equalized = xmalloc (size); memcpy (equalized, buf, size); equalizer_process_buffer (equalized, size, &driver_sound_params); buf = equalized; } if (softmixer_is_active () || softmixer_is_mono ()) { if (equalized) { softmixed = equalized; } else { softmixed = xmalloc (size); memcpy (softmixed, buf, size); } softmixer_process_buffer (softmixed, size, &driver_sound_params); buf = softmixed; } int played; played = hw.play (buf, size); if (played < 0) fatal ("Audio output error!"); if (softmixed && !equalized) free (softmixed); if (equalized) free (equalized); return played; } /* Get current time of the song in seconds. */ int audio_get_time () { return state != STATE_STOP ? out_buf_time_get (out_buf) : 0; } void audio_close () { if (audio_opened) { reset_sound_params (&req_sound_params); reset_sound_params (&driver_sound_params); hw.close (); if (need_audio_conversion) { audio_conv_destroy (&sound_conv); need_audio_conversion = 0; } audio_opened = 0; } } /* Try to initialize drivers from the list and fill funcs with * those of the first working driver. */ static void find_working_driver (lists_t_strs *drivers, struct hw_funcs *funcs) { int ix; memset (funcs, 0, sizeof(*funcs)); for (ix = 0; ix < lists_strs_size (drivers); ix += 1) { const char *name; name = lists_strs_at (drivers, ix); #ifdef HAVE_SNDIO if (!strcasecmp(name, "sndio")) { sndio_funcs (funcs); printf ("Trying SNDIO...\n"); if (funcs->init(&hw_caps)) return; } #endif #ifdef HAVE_OSS if (!strcasecmp(name, "oss")) { oss_funcs (funcs); printf ("Trying OSS...\n"); if (funcs->init(&hw_caps)) return; } #endif #ifdef HAVE_ALSA if (!strcasecmp(name, "alsa")) { alsa_funcs (funcs); printf ("Trying ALSA...\n"); if (funcs->init(&hw_caps)) return; } #endif #ifdef HAVE_JACK if (!strcasecmp(name, "jack")) { moc_jack_funcs (funcs); printf ("Trying JACK...\n"); if (funcs->init(&hw_caps)) return; } #endif #ifndef NDEBUG if (!strcasecmp(name, "null")) { null_funcs (funcs); printf ("Trying NULL...\n"); if (funcs->init(&hw_caps)) return; } #endif } fatal ("No valid sound driver!"); } static void print_output_capabilities (const struct output_driver_caps *caps LOGIT_ONLY) { char fmt_name[SFMT_STR_MAX] LOGIT_ONLY; logit ("Sound driver capabilities: channels %d - %d, formats: %s", caps->min_channels, caps->max_channels, sfmt_str(caps->formats, fmt_name, sizeof(fmt_name))); } void audio_initialize () { find_working_driver (options_get_list ("SoundDriver"), &hw); if (hw_caps.max_channels < hw_caps.min_channels) fatal ("Error initializing audio device: " "device reports incorrect number of channels."); if (!sound_format_ok (hw_caps.formats)) fatal ("Error initializing audio device: " "device reports no usable formats."); print_output_capabilities (&hw_caps); if (!options_get_bool ("Allow24bitOutput") && hw_caps.formats & (SFMT_S32 | SFMT_U32)) { logit ("Disabling 24bit modes because Allow24bitOutput is set to no."); hw_caps.formats &= ~(SFMT_S32 | SFMT_U32); if (!sound_format_ok (hw_caps.formats)) fatal ("No available sound formats after disabling 24bit modes. " "Consider setting Allow24bitOutput to yes."); } out_buf = out_buf_new (options_get_int("OutputBuffer") * 1024); softmixer_init(); equalizer_init(); plist_init (&playlist); plist_init (&shuffled_plist); plist_init (&queue); player_init (); } void audio_exit () { int rc; audio_stop (); if (hw.shutdown) hw.shutdown (); out_buf_free (out_buf); out_buf = NULL; plist_free (&playlist); plist_free (&shuffled_plist); plist_free (&queue); player_cleanup (); rc = pthread_mutex_destroy (&curr_playing_mtx); if (rc != 0) log_errno ("Can't destroy curr_playing_mtx", rc); rc = pthread_mutex_destroy (&plist_mtx); if (rc != 0) log_errno ("Can't destroy plist_mtx", rc); rc = pthread_mutex_destroy (&request_mtx); if (rc != 0) log_errno ("Can't destroy request_mtx", rc); if (last_stream_url) free (last_stream_url); softmixer_shutdown(); equalizer_shutdown(); } void audio_seek (const int sec) { int playing; LOCK (curr_playing_mtx); playing = curr_playing; UNLOCK (curr_playing_mtx); if (playing != -1 && state == STATE_PLAY) player_seek (sec); else logit ("Seeking when nothing is played."); } void audio_jump_to (const int sec) { int playing; LOCK (curr_playing_mtx); playing = curr_playing; UNLOCK (curr_playing_mtx); if (playing != -1 && state == STATE_PLAY) player_jump_to (sec); else logit ("Jumping when nothing is played."); } int audio_get_state () { return state; } int audio_get_prev_state () { return prev_state; } void audio_plist_add (const char *file) { LOCK (plist_mtx); plist_clear (&shuffled_plist); if (plist_find_fname(&playlist, file) == -1) plist_add (&playlist, file); else logit ("Wanted to add a file already present: %s", file); UNLOCK (plist_mtx); } void audio_queue_add (const char *file) { LOCK (plist_mtx); if (plist_find_fname(&queue, file) == -1) plist_add (&queue, file); else logit ("Wanted to add a file already present: %s", file); UNLOCK (plist_mtx); } void audio_plist_clear () { LOCK (plist_mtx); plist_clear (&shuffled_plist); plist_clear (&playlist); UNLOCK (plist_mtx); } void audio_queue_clear () { LOCK (plist_mtx); plist_clear (&queue); UNLOCK (plist_mtx); } /* Returned memory is malloc()ed. */ char *audio_get_sname () { char *sname; LOCK (curr_playing_mtx); sname = xstrdup (curr_playing_fname); UNLOCK (curr_playing_mtx); return sname; } int audio_get_mixer () { if (current_mixer == 2) return softmixer_get_value (); return hw.read_mixer (); } void audio_set_mixer (const int val) { if (!RANGE(0, val, 100)) { logit ("Tried to set mixer to volume out of range."); return; } if (current_mixer == 2) softmixer_set_value (val); else hw.set_mixer (val); } void audio_plist_delete (const char *file) { int num; LOCK (plist_mtx); num = plist_find_fname (&playlist, file); if (num != -1) plist_delete (&playlist, num); num = plist_find_fname (&shuffled_plist, file); if (num != -1) plist_delete (&shuffled_plist, num); UNLOCK (plist_mtx); } void audio_queue_delete (const char *file) { int num; LOCK (plist_mtx); num = plist_find_fname (&queue, file); if (num != -1) plist_delete (&queue, num); UNLOCK (plist_mtx); } /* Get the time of a file if the file is on the playlist and * the time is available. */ int audio_get_ftime (const char *file) { int i; int time; time_t mtime; mtime = get_mtime (file); LOCK (plist_mtx); i = plist_find_fname (&playlist, file); if (i != -1) { time = get_item_time (&playlist, i); if (time != -1) { if (playlist.items[i].mtime == mtime) { debug ("Found time for %s", file); UNLOCK (plist_mtx); return time; } logit ("mtime for %s has changed", file); } } UNLOCK (plist_mtx); return -1; } /* Set the time for a file on the playlist. */ void audio_plist_set_time (const char *file, const int time) { int i; LOCK (plist_mtx); if ((i = plist_find_fname(&playlist, file)) != -1) { plist_set_item_time (&playlist, i, time); playlist.items[i].mtime = get_mtime (file); debug ("Setting time for %s", file); } else logit ("Request for updating time for a file not present on the" " playlist!"); UNLOCK (plist_mtx); } /* Notify that the state was changed (used by the player). */ void audio_state_started_playing () { prev_state = state; state = STATE_PLAY; state_change (); } int audio_plist_get_serial () { int serial; LOCK (plist_mtx); serial = plist_get_serial (&playlist); UNLOCK (plist_mtx); return serial; } void audio_plist_set_serial (const int serial) { LOCK (plist_mtx); plist_set_serial (&playlist, serial); UNLOCK (plist_mtx); } /* Swap 2 files on the playlist. */ void audio_plist_move (const char *file1, const char *file2) { LOCK (plist_mtx); plist_swap_files (&playlist, file1, file2); UNLOCK (plist_mtx); } void audio_queue_move (const char *file1, const char *file2) { LOCK (plist_mtx); plist_swap_files (&queue, file1, file2); UNLOCK (plist_mtx); } /* Return a copy of the song queue. We cannot just return constant * pointer, because it will be used in a different thread. * It obviously needs to be freed after use. */ struct plist* audio_queue_get_contents () { struct plist *ret = (struct plist *)xmalloc (sizeof(struct plist)); plist_init (ret); LOCK (plist_mtx); plist_cat (ret, &queue); UNLOCK (plist_mtx); return ret; } struct file_tags *audio_get_curr_tags () { return player_get_curr_tags (); } char *audio_get_mixer_channel_name () { if (current_mixer == 2) return softmixer_name (); return hw.get_mixer_channel_name (); } void audio_toggle_mixer_channel () { current_mixer = (current_mixer + 1) % 3; if (current_mixer < 2) hw.toggle_mixer_channel (); } moc-2.6.0~svn-r3005/audio.h0000664000076400000500000001734213012456773014420 0ustar riesebiesrc#ifndef AUDIO_H #define AUDIO_H #include #ifdef __cplusplus extern "C" { #endif /** Sound formats. * * Sound format bits. Only one can be set in the format, the exception is * when we want to hold a list of supported formats - they can be bitwise-or'd. */ enum sfmt_fmt { SFMT_S8 = 0x00000001, /*!< signed 8-bit */ SFMT_U8 = 0x00000002, /*!< unsigned 8-bit */ SFMT_S16 = 0x00000004, /*!< signed 16-bit */ SFMT_U16 = 0x00000008, /*!< unsigned 16-bit */ SFMT_S32 = 0x00000010, /*!< signed 24-bit (LSB is 0) */ SFMT_U32 = 0x00000020, /*!< unsigned 24-bit (LSB set to 0) */ SFMT_FLOAT = 0x00000040 /*!< float in range -1.0 to 1.0 */ }; /** Sample endianness. * * Sample endianness - one of them must be set for 16-bit and 24-bit formats. */ enum sfmt_endianness { SFMT_LE = 0x00001000, /*!< little-endian */ SFMT_BE = 0x00002000, /*!< big-endian */ /** Define native endianness to SFMT_LE or SFMT_BE. */ #ifdef WORDS_BIGENDIAN SFMT_NE = SFMT_BE #else SFMT_NE = SFMT_LE #endif }; /** @name Masks for the sample format. * * Masks used to extract only one type of information from the sound format. */ /*@{*/ #define SFMT_MASK_FORMAT 0x00000fff /*!< sample format */ #define SFMT_MASK_ENDIANNESS 0x00003000 /*!< sample endianness */ /*@}*/ /** Return a value other than 0 if the sound format seems to be proper. */ #define sound_format_ok(f) (((f) & SFMT_MASK_FORMAT) \ && (((f) & (SFMT_S8 | SFMT_U8 | SFMT_FLOAT)) \ || (f) & SFMT_MASK_ENDIANNESS)) /** Change the sample format to new_fmt (without endianness). */ #define sfmt_set_fmt(f, new_fmt) (((f) & ~SFMT_MASK_FORMAT) | (new_fmt)) /** Change the sample format endianness to endian. */ #define sfmt_set_endian(f, endian) (((f) & ~SFMT_MASK_ENDIANNESS) | (endian)) /** Sound parameters. * * A structure describing sound parameters. The format is always PCM signed, * native endian for this machine. */ struct sound_params { int channels; /*!< Number of channels: 1 or 2 */ int rate; /*!< Rate in Hz */ long fmt; /*!< Format of the samples (SFMT_* bits) */ }; /** Output driver capabilities. * * A structure describing the output driver capabilities. */ struct output_driver_caps { int min_channels; /*!< Minimum number of channels */ int max_channels; /*!< Maximum number of channels */ long formats; /*!< Supported sample formats (or'd sfmt_fmt mask with endianness') */ }; /** \struct hw_funcs * Functions to control the audio "driver". * * The structure holds pointers to functions that must be provided by the audio * "driver". All functions are executed only by one thread, so you don't need * to worry if they are thread safe. */ struct hw_funcs { /** Initialize the driver. * * This function is invoked only once when the MOC server starts. * * \param caps Capabilities of the driver which must be filled by the * function. * \return 1 on success and 0 otherwise. */ int (*init) (struct output_driver_caps *caps); /** Clean up at exit. * * This function is invoked only once when the MOC server exits. The * audio device is not in use at this moment. The function should close * any opened devices and free any resources the driver allocated. * After this function was used, no other functions will be invoked. */ void (*shutdown) (); /** Open the sound device. * * This function should open the sound device with the proper * parameters. The function should return 1 on success and 0 otherwise. * After returning 1 functions like play(), get_buff_fill() can be used. * * The sample rate of the driver can differ from the requested rate. * If so, get_rate() should return the actual rate. * * \param sound_params Pointer to the sound_params structure holding * the required parameters. * \return 1 on success and 0 otherwise. */ int (*open) (struct sound_params *sound_params); /** Close the device. * * Request for closing the device. */ void (*close) (); /** Play sound. * * Play sound provided in the buffer. The sound is in the format * requested when the open() function was invoked. The function should * play all sound in the buffer. * * \param buff Pointer to the buffer with the sound. * \param size Size (in bytes) of the buffer. * * \return The number of bytes played or a value less than zero on * error. */ int (*play) (const char *buff, const size_t size); /** Read the volume setting. * * Read the current volume setting. This must work regardless if the * functions open()/close() where used. * * \return Volume value from 0% to 100%. */ int (*read_mixer) (); /** Set the volume setting. * * Set the volume. This must work regardless if the functions * open()/close() where used. * * \param vol Volume from 0% to 100%. */ void (*set_mixer) (int vol); /** Read the hardware/internal buffer fill. * * The function should return the number of bytes of any * hardware or internal buffers are filled. For example: if we play() * 4KB, but only 1KB was really played (could be heard by the user), * the function should return 3072 (3KB). * * \return Current hardware/internal buffer fill in bytes. */ int (*get_buff_fill) (); /** Stop playing immediately. * * Request that the sound should not be played. This should involve * flushing any internal buffer filled with data sent by the play() * function and resetting the device to flush its buffer (if possible). * * \return 1 on success or 0 otherwise. */ int (*reset) (); /** Get the current sample rate setting. * * Get the actual sample rate setting of the audio driver. * * \return Sample rate in Hz. */ int (*get_rate) (); /** Toggle the mixer channel. * * Toggle between the first and the second mixer channel. */ void (*toggle_mixer_channel) (); /** Get the mixer channel's name. * * Get the currently used mixer channel's name. * * \return malloc()ed channel's name. */ char * (*get_mixer_channel_name) (); }; /* Are the parameters p1 and p2 equal? */ #define sound_params_eq(p1, p2) ((p1).fmt == (p2).fmt \ && (p1).channels == (p2).channels && (p1).rate == (p2).rate) /* Maximum size of a string needed to hold the value returned by sfmt_str(). */ #define SFMT_STR_MAX 265 char *sfmt_str (const long format, char *msg, const size_t buf_size); int sfmt_Bps (const long format); int sfmt_same_bps (const long fmt1, const long fmt2); void audio_stop (); void audio_play (const char *fname); void audio_next (); void audio_prev (); void audio_pause (); void audio_unpause (); void audio_initialize (); void audio_exit (); void audio_seek (const int sec); void audio_jump_to (const int sec); int audio_open (struct sound_params *sound_params); int audio_send_buf (const char *buf, const size_t size); int audio_send_pcm (const char *buf, const size_t size); void audio_reset (); int audio_get_bpf (); int audio_get_bps (); int audio_get_buf_fill (); void audio_close (); int audio_get_time (); int audio_get_state (); int audio_get_prev_state (); void audio_plist_add (const char *file); void audio_plist_clear (); char *audio_get_sname (); void audio_set_mixer (const int val); int audio_get_mixer (); void audio_plist_delete (const char *file); int audio_get_ftime (const char *file); void audio_plist_set_time (const char *file, const int time); void audio_state_started_playing (); int audio_plist_get_serial (); void audio_plist_set_serial (const int serial); struct file_tags *audio_get_curr_tags (); char *audio_get_mixer_channel_name (); void audio_toggle_mixer_channel (); void audio_plist_move (const char *file1, const char *file2); void audio_queue_add (const char *file); void audio_queue_delete (const char *file); void audio_queue_clear (); void audio_queue_move (const char *file1, const char *file2); struct plist* audio_queue_get_contents (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/audio_conversion.c0000664000076400000500000004363613333460625016662 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * * Code for conversion between float and fixed point types is based on * libsamplerate: * Copyright (C) 2002-2004 Erik de Castro Lopo */ /* For future: audio conversion should be performed in order: * channels -> rate -> format */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifdef HAVE_SAMPLERATE # include #endif #define DEBUG #include "common.h" #include "audio_conversion.h" #include "log.h" #include "options.h" static void float_to_u8 (const float *in, unsigned char *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { float f = in[i] * INT32_MAX; if (f >= INT32_MAX) out[i] = UINT8_MAX; else if (f <= INT32_MIN) out[i] = 0; else out[i] = (unsigned int)((lrintf(f) >> 24) - INT8_MIN); } } static void float_to_s8 (const float *in, char *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { float f = in[i] * INT32_MAX; if (f >= INT32_MAX) out[i] = INT8_MAX; else if (f <= INT32_MIN) out[i] = INT8_MIN; else out[i] = lrintf(f) >> 24; } } static void float_to_u16 (const float *in, unsigned char *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { uint16_t *out_val = (uint16_t *)(out + i * sizeof (uint16_t)); float f = in[i] * INT32_MAX; if (f >= INT32_MAX) *out_val = UINT16_MAX; else if (f <= INT32_MIN) *out_val = 0; else *out_val = (unsigned int)((lrintf(f) >> 16) - INT16_MIN); } } static void float_to_s16 (const float *in, char *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { int16_t *out_val = (int16_t *)(out + i * sizeof (int16_t)); float f = in[i] * INT32_MAX; if (f >= INT32_MAX) *out_val = INT16_MAX; else if (f <= INT32_MIN) *out_val = INT16_MIN; else *out_val = lrintf(f) >> 16; } } static void float_to_u32 (const float *in, unsigned char *out, const size_t samples) { size_t i; /* maximum and minimum values of 32-bit samples */ const unsigned int U32_MAX = (1 << 24) - 1; const int S32_MAX = (1 << 23) - 1; const int S32_MIN = -(1 << 23); assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { uint32_t *out_val = (uint32_t *)(out + i * sizeof (uint32_t)); float f = in[i] * S32_MAX; if (f >= S32_MAX) *out_val = U32_MAX << 8; else if (f <= S32_MIN) *out_val = 0; else *out_val = (uint32_t)(lrintf(f) - S32_MIN) << 8; } } static void float_to_s32 (const float *in, char *out, const size_t samples) { size_t i; /* maximum and minimum values of 32-bit samples */ const int S32_MAX = (1 << 23) - 1; const int S32_MIN = -(1 << 23); assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) { int32_t *out_val = (int32_t *)(out + i * sizeof (int32_t)); float f = in[i] * S32_MAX; if (f >= S32_MAX) *out_val = S32_MAX << 8; else if (f <= S32_MIN) *out_val = S32_MIN * 256; else *out_val = lrintf(f) << 8; } } static void u8_to_float (const unsigned char *in, float *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = (((int)*in++) + INT8_MIN) / (float)(INT8_MAX + 1); } static void s8_to_float (const char *in, float *out, const size_t samples) { size_t i; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = *in++ / (float)(INT8_MAX + 1); } static void u16_to_float (const unsigned char *in, float *out, const size_t samples) { size_t i; const uint16_t *in_16 = (uint16_t *)in; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = ((int)*in_16++ + INT16_MIN) / (float)(INT16_MAX + 1); } static void s16_to_float (const char *in, float *out, const size_t samples) { size_t i; const int16_t *in_16 = (int16_t *)in; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = *in_16++ / (float)(INT16_MAX + 1); } static void u32_to_float (const unsigned char *in, float *out, const size_t samples) { size_t i; const uint32_t *in_32 = (uint32_t *)in; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = ((float)*in_32++ + (float)INT32_MIN) / ((float)INT32_MAX + 1.0); } static void s32_to_float (const char *in, float *out, const size_t samples) { size_t i; const int32_t *in_32 = (int32_t *)in; assert (in != NULL); assert (out != NULL); for (i = 0; i < samples; i++) out[i] = *in_32++ / ((float)INT32_MAX + 1.0); } /* Convert fixed point samples in format fmt (size in bytes) to float. * Size of converted sound is put in new_size. Returned memory is malloc()ed. */ static float *fixed_to_float (const char *buf, const size_t size, const long fmt, size_t *new_size) { float *out = NULL; char fmt_name[SFMT_STR_MAX]; assert ((fmt & SFMT_MASK_FORMAT) != SFMT_FLOAT); switch (fmt & SFMT_MASK_FORMAT) { case SFMT_U8: *new_size = sizeof(float) * size; out = (float *)xmalloc (*new_size); u8_to_float ((unsigned char *)buf, out, size); break; case SFMT_S8: *new_size = sizeof(float) * size; out = (float *)xmalloc (*new_size); s8_to_float (buf, out, size); break; case SFMT_U16: *new_size = sizeof(float) * size / 2; out = (float *)xmalloc (*new_size); u16_to_float ((unsigned char *)buf, out, size / 2); break; case SFMT_S16: *new_size = sizeof(float) * size / 2; out = (float *)xmalloc (*new_size); s16_to_float (buf, out, size / 2); break; case SFMT_U32: *new_size = sizeof(float) * size / 4; out = (float *)xmalloc (*new_size); u32_to_float ((unsigned char *)buf, out, size / 4); break; case SFMT_S32: *new_size = sizeof(float) * size / 4; out = (float *)xmalloc (*new_size); s32_to_float (buf, out, size / 4); break; default: error ("Can't convert from %s to float!", sfmt_str (fmt, fmt_name, sizeof (fmt_name))); abort (); } return out; } /* Convert float samples to fixed point format fmt. Returned samples of size * new_size bytes is malloc()ed. */ static char *float_to_fixed (const float *buf, const size_t samples, const long fmt, size_t *new_size) { char fmt_name[SFMT_STR_MAX]; char *new_snd = NULL; assert ((fmt & SFMT_MASK_FORMAT) != SFMT_FLOAT); switch (fmt & SFMT_MASK_FORMAT) { case SFMT_U8: *new_size = samples; new_snd = (char *)xmalloc (samples); float_to_u8 (buf, (unsigned char *)new_snd, samples); break; case SFMT_S8: *new_size = samples; new_snd = (char *)xmalloc (samples); float_to_s8 (buf, new_snd, samples); break; case SFMT_U16: *new_size = samples * 2; new_snd = (char *)xmalloc (*new_size); float_to_u16 (buf, (unsigned char *)new_snd, samples); break; case SFMT_S16: *new_size = samples * 2; new_snd = (char *)xmalloc (*new_size); float_to_s16 (buf, new_snd, samples); break; case SFMT_U32: *new_size = samples * 4; new_snd = (char *)xmalloc (*new_size); float_to_u32 (buf, (unsigned char *)new_snd, samples); break; case SFMT_S32: *new_size = samples * 4; new_snd = (char *)xmalloc (*new_size); float_to_s32 (buf, new_snd, samples); break; default: error ("Can't convert from float to %s!", sfmt_str (fmt, fmt_name, sizeof (fmt_name))); abort (); } return new_snd; } static void change_sign_8 (uint8_t *buf, const size_t samples) { size_t i; for (i = 0; i < samples; i++) *buf++ ^= 1 << 7; } static void change_sign_16 (uint16_t *buf, const size_t samples) { size_t i; for (i = 0; i < samples; i++) *buf++ ^= 1 << 15; } static void change_sign_32 (uint32_t *buf, const size_t samples) { size_t i; for (i = 0; i < samples; i++) *buf++ ^= 1 << 31; } /* Change the signs of samples in format *fmt. Also changes fmt to the new * format. */ static void change_sign (char *buf, const size_t size, long *fmt) { char fmt_name[SFMT_STR_MAX]; switch (*fmt & SFMT_MASK_FORMAT) { case SFMT_S8: case SFMT_U8: change_sign_8 ((uint8_t *)buf, size); if (*fmt & SFMT_S8) *fmt = sfmt_set_fmt (*fmt, SFMT_U8); else *fmt = sfmt_set_fmt (*fmt, SFMT_S8); break; case SFMT_S16: case SFMT_U16: change_sign_16 ((uint16_t *)buf, size / 2); if (*fmt & SFMT_S16) *fmt = sfmt_set_fmt (*fmt, SFMT_U16); else *fmt = sfmt_set_fmt (*fmt, SFMT_S16); break; case SFMT_S32: case SFMT_U32: change_sign_32 ((uint32_t *)buf, size/4); if (*fmt & SFMT_S32) *fmt = sfmt_set_fmt (*fmt, SFMT_U32); else *fmt = sfmt_set_fmt (*fmt, SFMT_S32); break; default: error ("Request for changing sign of unknown format: %s", sfmt_str (*fmt, fmt_name, sizeof (fmt_name))); abort (); } } void audio_conv_bswap_16 (int16_t *buf, const size_t num) { size_t i; for (i = 0; i < num; i++) buf[i] = bswap_16 (buf[i]); } void audio_conv_bswap_32 (int32_t *buf, const size_t num) { size_t i; for (i = 0; i < num; i++) buf[i] = bswap_32 (buf[i]); } /* Swap endianness of fixed point samples. */ static void swap_endian (char *buf, const size_t size, const long fmt) { if ((fmt & (SFMT_S8 | SFMT_U8 | SFMT_FLOAT))) return; switch (fmt & SFMT_MASK_FORMAT) { case SFMT_S16: case SFMT_U16: audio_conv_bswap_16 ((int16_t *)buf, size / 2); break; case SFMT_S32: case SFMT_U32: audio_conv_bswap_32 ((int32_t *)buf, size / 4); break; default: error ("Can't convert to native endian!"); abort (); /* we can't do anything smarter */ } } /* Initialize the audio_conversion structure for conversion between parameters * from and to. Return 0 on error. */ int audio_conv_new (struct audio_conversion *conv, const struct sound_params *from, const struct sound_params *to) { assert (from->rate != to->rate || from->fmt != to->fmt || from->channels != to->channels); if (from->channels != to->channels) { /* the only conversion we can do */ if (!(from->channels == 1 && to->channels == 2)) { error ("Can't change number of channels (%d to %d)!", from->channels, to->channels); return 0; } } if (from->rate != to->rate) { #ifdef HAVE_SAMPLERATE int err; int resample_type = -1; char *method = options_get_symb ("ResampleMethod"); if (!strcasecmp(method, "SincBestQuality")) resample_type = SRC_SINC_BEST_QUALITY; else if (!strcasecmp(method, "SincMediumQuality")) resample_type = SRC_SINC_MEDIUM_QUALITY; else if (!strcasecmp(method, "SincFastest")) resample_type = SRC_SINC_FASTEST; else if (!strcasecmp(method, "ZeroOrderHold")) resample_type = SRC_ZERO_ORDER_HOLD; else if (!strcasecmp(method, "Linear")) resample_type = SRC_LINEAR; else fatal ("Bad ResampleMethod option: %s", method); conv->src_state = src_new (resample_type, to->channels, &err); if (!conv->src_state) { error ("Can't resample from %dHz to %dHz: %s", from->rate, to->rate, src_strerror (err)); return 0; } #else error ("Resampling not supported!"); return 0; #endif } #ifdef HAVE_SAMPLERATE else conv->src_state = NULL; #endif conv->from = *from; conv->to = *to; #ifdef HAVE_SAMPLERATE conv->resample_buf = NULL; conv->resample_buf_nsamples = 0; #endif return 1; } #ifdef HAVE_SAMPLERATE static float *resample_sound (struct audio_conversion *conv, const float *buf, const size_t samples, const int nchannels, size_t *resampled_samples) { SRC_DATA resample_data; float *output; float *new_input_start; int output_samples = 0; resample_data.end_of_input = 0; resample_data.src_ratio = conv->to.rate / (double)conv->from.rate; resample_data.input_frames = samples / nchannels + conv->resample_buf_nsamples / nchannels; resample_data.output_frames = resample_data.input_frames * resample_data.src_ratio; assert (conv->resample_buf || conv->resample_buf_nsamples == 0); conv->resample_buf = xrealloc (conv->resample_buf, sizeof(float) * nchannels * resample_data.input_frames); new_input_start = conv->resample_buf + conv->resample_buf_nsamples; output = (float *)xmalloc (sizeof(float) * resample_data.output_frames * nchannels); /*debug ("Resampling %lu bytes of data by ratio %f", (unsigned long)size, resample_data.src_ratio);*/ memcpy (new_input_start, buf, samples * sizeof(float)); resample_data.data_in = conv->resample_buf; resample_data.data_out = output; do { int err; if ((err = src_process(conv->src_state, &resample_data))) { error ("Can't resample: %s", src_strerror (err)); free (output); return NULL; } resample_data.data_in += resample_data.input_frames_used * nchannels; resample_data.input_frames -= resample_data.input_frames_used; resample_data.data_out += resample_data.output_frames_gen * nchannels; resample_data.output_frames -= resample_data.output_frames_gen; output_samples += resample_data.output_frames_gen * nchannels; } while (resample_data.input_frames && resample_data.output_frames_gen && resample_data.output_frames); *resampled_samples = output_samples; if (resample_data.input_frames) { conv->resample_buf_nsamples = resample_data.input_frames * nchannels; if (conv->resample_buf != resample_data.data_in) { float *new; new = (float *)xmalloc (sizeof(float) * conv->resample_buf_nsamples); memcpy (new, resample_data.data_in, sizeof(float) * conv->resample_buf_nsamples); free (conv->resample_buf); conv->resample_buf = new; } } else { free (conv->resample_buf); conv->resample_buf = NULL; conv->resample_buf_nsamples = 0; } return output; } #endif /* Double the channels from */ static char *mono_to_stereo (const char *mono, const size_t size, const long format) { int Bps = sfmt_Bps (format); size_t i; char *stereo; stereo = (char *)xmalloc (size * 2); for (i = 0; i < size; i += Bps) { memcpy (stereo + (i * 2), mono + i, Bps); memcpy (stereo + (i * 2 + Bps), mono + i, Bps); } return stereo; } static int16_t *s32_to_s16 (int32_t *in, const size_t samples) { size_t i; int16_t *new; new = (int16_t *)xmalloc (samples * 2); for (i = 0; i < samples; i++) new[i] = in[i] >> 16; return new; } static uint16_t *u32_to_u16 (uint32_t *in, const size_t samples) { size_t i; uint16_t *new; new = (uint16_t *)xmalloc (samples * 2); for (i = 0; i < samples; i++) new[i] = in[i] >> 16; return new; } /* Do the sound conversion. buf of length size is the sample buffer to * convert and the size of the converted sound is put into *conv_len. * Return the converted sound in malloc()ed memory. */ char *audio_conv (struct audio_conversion *conv, const char *buf, const size_t size, size_t *conv_len) { char *curr_sound; long curr_sfmt = conv->from.fmt; *conv_len = size; curr_sound = (char *)xmalloc (size); memcpy (curr_sound, buf, size); if (!(curr_sfmt & SFMT_NE)) { swap_endian (curr_sound, *conv_len, curr_sfmt); curr_sfmt = sfmt_set_endian (curr_sfmt, SFMT_NE); } /* Special case (optimization): if we only need to convert 32bit samples * to 16bit, we can do it very simply and quickly. */ if ((curr_sfmt & (SFMT_S32 | SFMT_U32)) && (conv->to.fmt & (SFMT_S16 | SFMT_U16)) && conv->from.rate == conv->to.rate) { char *new_sound; if ((curr_sfmt & SFMT_MASK_FORMAT) == SFMT_S32) { new_sound = (char *)s32_to_s16 ((int32_t *)curr_sound, *conv_len / 4); curr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_S16); } else { new_sound = (char *)u32_to_u16 ((uint32_t *)curr_sound, *conv_len / 4); curr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_U16); } if (curr_sound != buf) free (curr_sound); curr_sound = new_sound; *conv_len /= 2; logit ("Fast conversion!"); } /* convert to float if necessary */ if ((conv->from.rate != conv->to.rate || (conv->to.fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT || !sfmt_same_bps(conv->to.fmt, curr_sfmt)) && (curr_sfmt & SFMT_MASK_FORMAT) != SFMT_FLOAT) { char *new_sound; new_sound = (char *)fixed_to_float (curr_sound, *conv_len, curr_sfmt, conv_len); curr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_FLOAT); if (curr_sound != buf) free (curr_sound); curr_sound = new_sound; } #ifdef HAVE_SAMPLERATE if (conv->from.rate != conv->to.rate) { char *new_sound = (char *)resample_sound (conv, (float *)curr_sound, *conv_len / sizeof(float), conv->to.channels, conv_len); *conv_len *= sizeof(float); if (curr_sound != buf) free (curr_sound); curr_sound = new_sound; } #endif if ((curr_sfmt & SFMT_MASK_FORMAT) != (conv->to.fmt & SFMT_MASK_FORMAT)) { if (sfmt_same_bps(curr_sfmt, conv->to.fmt)) change_sign (curr_sound, size, &curr_sfmt); else { char *new_sound; assert (curr_sfmt & SFMT_FLOAT); new_sound = float_to_fixed ((float *)curr_sound, *conv_len / sizeof(float), conv->to.fmt, conv_len); curr_sfmt = sfmt_set_fmt (curr_sfmt, conv->to.fmt); if (curr_sound != buf) free (curr_sound); curr_sound = new_sound; } } if ((curr_sfmt & SFMT_MASK_ENDIANNESS) != (conv->to.fmt & SFMT_MASK_ENDIANNESS)) { swap_endian (curr_sound, *conv_len, curr_sfmt); curr_sfmt = sfmt_set_endian (curr_sfmt, conv->to.fmt & SFMT_MASK_ENDIANNESS); } if (conv->from.channels == 1 && conv->to.channels == 2) { char *new_sound; new_sound = mono_to_stereo (curr_sound, *conv_len, curr_sfmt); *conv_len *= 2; if (curr_sound != buf) free (curr_sound); curr_sound = new_sound; } return curr_sound; } void audio_conv_destroy (struct audio_conversion *conv ASSERT_ONLY) { assert (conv != NULL); #ifdef HAVE_SAMPLERATE if (conv->resample_buf) free (conv->resample_buf); if (conv->src_state) src_delete (conv->src_state); #endif } moc-2.6.0~svn-r3005/audio_conversion.h0000664000076400000500000000161512236532121016646 0ustar riesebiesrc#ifndef AUDIO_CONVERSION_H #define AUDIO_CONVERSION_H #ifdef HAVE_STDINT_H # include #endif #include #ifdef HAVE_SAMPLERATE # include #endif #include "audio.h" #ifdef __cplusplus extern "C" { #endif struct audio_conversion { struct sound_params from; struct sound_params to; #ifdef HAVE_SAMPLERATE SRC_STATE *src_state; float *resample_buf; size_t resample_buf_nsamples; /* in samples ( sizeof(float) ) */ #endif }; int audio_conv_new (struct audio_conversion *conv, const struct sound_params *from, const struct sound_params *to); char *audio_conv (struct audio_conversion *conv, const char *buf, const size_t size, size_t *conv_len); void audio_conv_destroy (struct audio_conversion *conv); void audio_conv_bswap_16 (int16_t *buf, const size_t num); void audio_conv_bswap_32 (int32_t *buf, const size_t num); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/common.c0000664000076400000500000002230613333460625014573 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" # undef malloc #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYSLOG #include #endif #include "common.h" #include "server.h" #include "interface.h" #include "interface_elements.h" #include "log.h" #include "options.h" static int im_server = 0; /* Am I the server? */ void internal_error (const char *file, int line, const char *function, const char *format, ...) { int saved_errno = errno; va_list va; char *msg; va_start (va, format); msg = format_msg_va (format, va); va_end (va); if (im_server) server_error (file, line, function, msg); else interface_error (msg); free (msg); errno = saved_errno; } /* End program with a message. Use when an error occurs and we can't recover. * If we're the server, then also log the message to the system log. */ void internal_fatal (const char *file LOGIT_ONLY, int line LOGIT_ONLY, const char *function LOGIT_ONLY, const char *format, ...) { va_list va; char *msg; windows_reset (); va_start (va, format); msg = format_msg_va (format, va); fprintf (stderr, "\nFATAL_ERROR: %s\n\n", msg); #ifndef NDEBUG internal_logit (file, line, function, "FATAL ERROR: %s", msg); #endif va_end (va); log_close (); #ifdef HAVE_SYSLOG if (im_server) syslog (LOG_USER|LOG_ERR, "%s", msg); #endif free (msg); exit (EXIT_FATAL); } void *xmalloc (size_t size) { void *p; #ifndef HAVE_MALLOC size = MAX(1, size); #endif if ((p = malloc(size)) == NULL) fatal ("Can't allocate memory!"); return p; } void *xcalloc (size_t nmemb, size_t size) { void *p; if ((p = calloc(nmemb, size)) == NULL) fatal ("Can't allocate memory!"); return p; } void *xrealloc (void *ptr, const size_t size) { void *p; p = realloc (ptr, size); if (!p && size != 0) fatal ("Can't allocate memory!"); return p; } char *xstrdup (const char *s) { char *n; if (s && (n = strdup(s)) == NULL) fatal ("Can't allocate memory!"); return s ? n : NULL; } /* Sleep for the specified number of 'ticks'. */ void xsleep (size_t ticks, size_t ticks_per_sec) { assert(ticks_per_sec > 0); if (ticks > 0) { int rc; struct timespec delay = {.tv_sec = ticks}; if (ticks_per_sec > 1) { uint64_t nsecs; delay.tv_sec /= ticks_per_sec; nsecs = ticks % ticks_per_sec; if (nsecs > 0) { assert (nsecs < UINT64_MAX / UINT64_C(1000000000)); delay.tv_nsec = nsecs * UINT64_C(1000000000); delay.tv_nsec /= ticks_per_sec; } } do { rc = nanosleep (&delay, &delay); if (rc == -1 && errno != EINTR) fatal ("nanosleep() failed: %s", xstrerror (errno)); } while (rc != 0); } } #if !HAVE_DECL_STRERROR_R static pthread_mutex_t xstrerror_mtx = PTHREAD_MUTEX_INITIALIZER; #endif #if !HAVE_DECL_STRERROR_R /* Return error message in malloc() buffer (for strerror(3)). */ char *xstrerror (int errnum) { char *result; /* The client is not threaded. */ if (!im_server) return xstrdup (strerror (errnum)); LOCK (xstrerror_mtx); result = xstrdup (strerror (errnum)); UNLOCK (xstrerror_mtx); return result; } #endif #if HAVE_DECL_STRERROR_R /* Return error message in malloc() buffer (for strerror_r(3)). */ char *xstrerror (int errnum) { int saved_errno = errno; char *err_str, err_buf[256]; #ifdef STRERROR_R_CHAR_P /* strerror_r(3) is GNU variant. */ err_str = strerror_r (errnum, err_buf, sizeof (err_buf)); #else /* strerror_r(3) is XSI variant. */ if (strerror_r (errnum, err_buf, sizeof (err_buf)) < 0) { logit ("Error %d occurred obtaining error description for %d", errno, errnum); strcpy (err_buf, "Error occurred obtaining error description"); } err_str = err_buf; #endif errno = saved_errno; return xstrdup (err_str); } #endif /* A signal(2) which is both thread safe and POSIXly well defined. */ void xsignal (int signum, void (*func)(int)) { struct sigaction act; act.sa_handler = func; act.sa_flags = 0; sigemptyset (&act.sa_mask); if (sigaction(signum, &act, 0) == -1) fatal ("sigaction() failed: %s", xstrerror (errno)); } void set_me_server () { im_server = 1; } char *str_repl (char *target, const char *oldstr, const char *newstr) { size_t oldstr_len = strlen(oldstr); size_t newstr_len = strlen(newstr); size_t target_len = strlen(target); size_t target_max = target_len; size_t s, p; char *needle; for (s = 0; (needle = strstr(target + s, oldstr)) != NULL; s = p + newstr_len) { target_len += newstr_len - oldstr_len; p = needle - target; if (target_len + 1 > target_max) { target_max = MAX(target_len + 1, target_max * 2); target = xrealloc(target, target_max); } memmove(target + p + newstr_len, target + p + oldstr_len, target_len - p - newstr_len + 1); memcpy(target + p, newstr, newstr_len); } target = xrealloc(target, target_len + 1); return target; } /* Extract a substring starting at 'src' for length 'len' and remove * any leading and trailing whitespace. Return NULL if unable. */ char *trim (const char *src, size_t len) { char *result; const char *first, *last; for (last = &src[len - 1]; last >= src; last -= 1) { if (!isspace (*last)) break; } if (last < src) return NULL; for (first = src; first <= last; first += 1) { if (!isspace (*first)) break; } if (first > last) return NULL; last += 1; result = xcalloc (last - first + 1, sizeof (char)); strncpy (result, first, last - first); result[last - first] = 0x00; return result; } /* Format argument values according to 'format' and return it as a * malloc()ed string. */ char *format_msg (const char *format, ...) { char *result; va_list va; va_start (va, format); result = format_msg_va (format, va); va_end (va); return result; } /* Format a vararg list according to 'format' and return it as a * malloc()ed string. */ char *format_msg_va (const char *format, va_list va) { int len; char *result; va_list va_copy; va_copy (va_copy, va); len = vsnprintf (NULL, 0, format, va_copy) + 1; va_end (va_copy); result = xmalloc (len); vsnprintf (result, len, format, va); return result; } /* Return true iff the argument would be a syntactically valid symbol. * (Note that the so-called "peculiar indentifiers" are disallowed here.) */ bool is_valid_symbol (const char *candidate) { size_t len; bool result; const char *first = "+-.0123456789@"; const char *valid = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "@?!.+-*/<=>:$%^&_~"; result = false; len = strlen (candidate); if (len > 0 && len == strspn (candidate, valid) && strchr (first, candidate[0]) == NULL) result = true; return result; } /* Return path to a file in MOC config directory. NOT THREAD SAFE */ char *create_file_name (const char *file) { int rc; static char fname[PATH_MAX]; char *moc_dir = options_get_str ("MOCDir"); if (moc_dir[0] == '~') rc = snprintf(fname, sizeof(fname), "%s/%s/%s", get_home (), (moc_dir[1] == '/') ? moc_dir + 2 : moc_dir + 1, file); else rc = snprintf(fname, sizeof(fname), "%s/%s", moc_dir, file); if (rc >= ssizeof(fname)) fatal ("Path too long!"); return fname; } int get_realtime (struct timespec *ts) { int result; #ifdef HAVE_CLOCK_GETTIME result = clock_gettime (CLOCK_REALTIME, ts); #else struct timeval tv; result = gettimeofday (&tv, NULL); if (result == 0) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000L; } #endif return result; } /* Convert time in second to min:sec text format. 'buff' must be at least 32 chars long. */ void sec_to_min (char *buff, const int seconds) { assert (seconds >= 0); if (seconds < 6000) { /* the time is less than 99:59 */ int min, sec; min = seconds / 60; sec = seconds % 60; snprintf (buff, 32, "%02d:%02d", min, sec); } else if (seconds < 10000 * 60) /* the time is less than 9999 minutes */ snprintf (buff, 32, "%4dm", seconds/60); else strcpy (buff, "!!!!!"); } /* Determine and return the path of the user's home directory. */ const char *get_home () { static const char *home = NULL; struct passwd *passwd; if (home == NULL) { home = xstrdup (getenv ("HOME")); if (home == NULL) { errno = 0; passwd = getpwuid (geteuid ()); if (passwd) home = xstrdup (passwd->pw_dir); else if (errno != 0) { char *err = xstrerror (errno); logit ("getpwuid(%d): %s", geteuid (), err); free (err); } } } return home; } void common_cleanup () { #if !HAVE_DECL_STRERROR_R int rc; if (im_server) return; rc = pthread_mutex_destroy (&xstrerror_mtx); if (rc != 0) logit ("Can't destroy xstrerror_mtx: %s", strerror (rc)); #endif } moc-2.6.0~svn-r3005/common.h0000664000076400000500000001116513333136304014573 0ustar riesebiesrc/* * The purpose of this header is to provide common functions and macros * used throughout MOC code. It also provides (x-prefixed) functions * which augment or adapt their respective system functions with error * checking and the like. */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include "compat.h" /* Suppress overly-enthusiastic GNU variadic macro extensions warning. */ #if defined(__clang__) && HAVE_VARIADIC_MACRO_WARNING # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #endif struct timespec; #ifdef HAVE_FUNC_ATTRIBUTE_FORMAT # define ATTR_PRINTF(x,y) __attribute__ ((format (printf, x, y))) #else # define ATTR_PRINTF(...) #endif #ifdef HAVE_FUNC_ATTRIBUTE_NORETURN # define ATTR_NORETURN __attribute__((noreturn)) #else # define ATTR_NORETURN #endif #ifdef HAVE_VAR_ATTRIBUTE_UNUSED # define ATTR_UNUSED __attribute__((unused)) #else # define ATTR_UNUSED #endif #ifndef GCC_VERSION #define GCC_VERSION (__GNUC__ * 10000 + \ __GNUC_MINOR__ * 100 + \ __GNUC_PATCHLEVEL__) #endif /* These macros allow us to use the appropriate method for manipulating * GCC's diagnostic pragmas depending on the compiler's version. */ #if GCC_VERSION >= 40200 # define GCC_DIAG_STR(s) #s # define GCC_DIAG_JOINSTR(x,y) GCC_DIAG_STR(x ## y) # define GCC_DIAG_DO_PRAGMA(x) _Pragma (#x) # define GCC_DIAG_PRAGMA(x) GCC_DIAG_DO_PRAGMA(GCC diagnostic x) # if GCC_VERSION >= 40600 # define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(push) \ GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x)) # define GCC_DIAG_ON(x) GCC_DIAG_PRAGMA(pop) # else # define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x)) # define GCC_DIAG_ON(x) GCC_DIAG_PRAGMA(warning GCC_DIAG_JOINSTR(-W,x)) # endif #else # define GCC_DIAG_OFF(x) # define GCC_DIAG_ON(x) #endif #ifdef HAVE_FORMAT_TRUNCATION_WARNING # define SUPPRESS_FORMAT_TRUNCATION_WARNING GCC_DIAG_OFF(format-truncation) # define UNSUPPRESS_FORMAT_TRUNCATION_WARNING GCC_DIAG_ON(format-truncation) #else # define SUPPRESS_FORMAT_TRUNCATION_WARNING # define UNSUPPRESS_FORMAT_TRUNCATION_WARNING #endif #define CONFIG_DIR ".moc" #define LOCK(mutex) pthread_mutex_lock (&mutex) #define UNLOCK(mutex) pthread_mutex_unlock (&mutex) #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define ssizeof(x) ((ssize_t) sizeof(x)) /* Maximal string length sent/received. */ #define MAX_SEND_STRING 4096 /* Exit status on fatal error. */ #define EXIT_FATAL 2 #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef LIMIT #define LIMIT(val, lim) ((val) >= 0 && (val) < (lim)) #endif #ifndef RANGE #define RANGE(min, val, max) ((val) >= (min) && (val) <= (max)) #endif #ifndef CLAMP #define CLAMP(min, val, max) ((val) < (min) ? (min) : \ (val) > (max) ? (max) : (val)) #endif #ifdef NDEBUG #define error(...) \ internal_error (NULL, 0, NULL, ## __VA_ARGS__) #define fatal(...) \ internal_fatal (NULL, 0, NULL, ## __VA_ARGS__) #define ASSERT_ONLY ATTR_UNUSED #else #define error(...) \ internal_error (__FILE__, __LINE__, __func__, ## __VA_ARGS__) #define fatal(...) \ internal_fatal (__FILE__, __LINE__, __func__, ## __VA_ARGS__) #define ASSERT_ONLY #endif #ifndef STRERROR_FN # define STRERROR_FN xstrerror #endif #define error_errno(format, errnum) \ do { \ char *err##__LINE__ = STRERROR_FN (errnum); \ error (format ": %s", err##__LINE__); \ free (err##__LINE__); \ } while (0) #ifdef __cplusplus extern "C" { #endif void *xmalloc (size_t size); void *xcalloc (size_t nmemb, size_t size); void *xrealloc (void *ptr, const size_t size); char *xstrdup (const char *s); void xsleep (size_t ticks, size_t ticks_per_sec); char *xstrerror (int errnum); void xsignal (int signum, void (*func)(int)); void internal_error (const char *file, int line, const char *function, const char *format, ...) ATTR_PRINTF(4, 5); void internal_fatal (const char *file, int line, const char *function, const char *format, ...) ATTR_NORETURN ATTR_PRINTF(4, 5); void set_me_server (); char *str_repl (char *target, const char *oldstr, const char *newstr); char *trim (const char *src, size_t len); char *format_msg (const char *format, ...); char *format_msg_va (const char *format, va_list va); bool is_valid_symbol (const char *candidate); char *create_file_name (const char *file); int get_realtime (struct timespec *ts); void sec_to_min (char *buff, const int seconds); const char *get_home (); void common_cleanup (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/compat.c0000664000076400000500000000230513022625703014556 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* Various functions which some systems lack. */ #ifndef HAVE_STRCASESTR #include #include /* Case insensitive version of strstr(). */ char *strcasestr (const char *haystack, const char *needle) { char *haystack_i, *needle_i; char *c; char *res; haystack_i = xstrdup (haystack); needle_i = xstrdup (needle); c = haystack_i; while (*c) { *c = tolower (*c); c++; } c = needle_i; while (*c) { *c = tolower (*c); c++; } res = strstr (haystack_i, needle_i); free (haystack_i); free (needle_i); return res ? res - haystack_i + (char *)haystack : NULL; } #endif /* This is required to prevent an "empty translation unit" warning if neither strcasestr() nor clock_gettime() get defined. */ #if defined(HAVE_STRCASESTR) && defined(HAVE_CLOCK_GETTIME) int compat_is_empty; #endif moc-2.6.0~svn-r3005/compat.h0000664000076400000500000000311613022625703014564 0ustar riesebiesrc/* * The purpose of this header is to provide functions and macros which * MOC code expects but which are missing or broken on the host system. * * This header should be included by all code before any other MOC * headers (except 'compiler.h'). Therefore, it is included once by * 'common.h' which is itself included by all code. */ #ifndef COMPAT_H #define COMPAT_H #ifdef HAVE_BYTESWAP_H # include #else /* Given an unsigned 16-bit argument X, return the value corresponding to X with reversed byte order. */ # define bswap_16(x) ((((x) & 0x00FF) << 8) | \ (((x) & 0xFF00) >> 8)) /* Given an unsigned 32-bit argument X, return the value corresponding to X with reversed byte order. */ # define bswap_32(x) ((((x) & 0x000000FF) << 24) | \ (((x) & 0x0000FF00) << 8) | \ (((x) & 0x00FF0000) >> 8) | \ (((x) & 0xFF000000) >> 24)) #endif #ifndef SUN_LEN #define SUN_LEN(p) \ ((sizeof *(p)) - sizeof((p)->sun_path) + strlen ((p)->sun_path)) #endif /* Maximum path length, we don't consider exceptions like mounted NFS */ #ifndef PATH_MAX # if defined(_POSIX_PATH_MAX) # define PATH_MAX _POSIX_PATH_MAX /* Posix */ # elif defined(MAXPATHLEN) # define PATH_MAX MAXPATHLEN /* Solaris? Also linux...*/ # else # define PATH_MAX 4096 /* Suppose, we have 4096 */ # endif #endif #ifdef __cplusplus extern "C" { #endif #if !HAVE_DECL_STRCASESTR && !defined(__cplusplus) char *strcasestr (const char *haystack, const char *needle); #endif #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/compiler.h0000664000076400000500000000250612724717244015127 0ustar riesebiesrc/* * The purpose of this header is to configure the compiler and system * headers the way we want them to be. It should be included in every * source code file *before* any system headers are included, and thus * an include for it is automatically appended to the 'config.h' header * by 'configure'. * * It is also included by 'configure' tests to ensure that any system * headers *they* include will be configured consistantly and symbols * they declare will not be exposed differently in the tests and the * code thus causing the configuration macros defined in 'config.h' * to be mismatched with the included system headers. * * Because it is used in both places, it should not include any code * which is relevant only to MOC code. */ #ifndef COMPILER_H #define COMPILER_H /* _XOPEN_SOURCE is known to break compilation on OpenBSD. */ #ifndef OPENBSD # if defined(_XOPEN_SOURCE) && _XOPEN_SOURCE < 600 # undef _XOPEN_SOURCE # endif # ifndef _XOPEN_SOURCE # define _XOPEN_SOURCE 600 # endif #endif /* _XOPEN_SOURCE_EXTENDED is known to break compilation on FreeBSD. */ #ifndef FREEBSD # define _XOPEN_SOURCE_EXTENDED 1 #endif /* Require POSIX.1-2001 or better. */ #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE < 200112L # undef _POSIX_C_SOURCE #endif #ifndef _POSIX_C_SOURCE # define _POSIX_C_SOURCE 200112L #endif #endif moc-2.6.0~svn-r3005/config.example.in0000664000076400000500000007000413224101463016353 0ustar riesebiesrc# This is a configuration file for the MOC player. It should be named # 'config' and placed in the ~/.moc directory. As this file can specify # commands which invoke other applications, MOC will refuse to start if it # is not owned by either root or the current user, or if it is writable by # anyone other than its owner. # Comments begin with '#'. All options are given with their default # values, and therefore commented. If you change the value from the # default you must uncomment the line to have the new value take effect. # # You can use quotes and escape ('\') in parameters. # # You can have variable values substituted by enclosing the variable name # as "${...}". (This only applies to the portion of the option following # the '='.) Variables are substituted first from the environment then, # if not found, from the configuration options. (Note that the value of # a configuration option substituted is that which it has at the time the # substitution variable is encountered.) If there is a naming conflict # between an environment and configuration variable, you may be able to # resolve it by using lowercase as the environment variable matches are # case-sensitive whereas the configuration variables are not. # # You can also use the form "${...:-...}" where the value in the second # position will be substituted if the variable name given in the first # position is unset or null. # # So, for example: # # MusicDir = /music/${USER:-public} # Fastdir1 = ${MusicDir}/mp3/rock # Fastdir2 = ${MusicDir}/mp3/electronic # Fastdir3 = ${MusicDir}/mp3/rap # Fastdir4 = ${MusicDir}/mp3/etc # # Variable names are limited to those accepted by the BASH shell; that # is, those comprising the upper- and lowercase ASCII characters, digits # and the underscore. # # If you need to use the "${" sequence for any other purpose, write "$${" # and it will be replaced by "${" and not treated as a substitution. # # Some options take lists of strings as their values. The strings are # separated by colons. Additional strings can be appended to the list # using "+=" in place of a plain "=" to assign the value. For an example, # see the XTerms option. # # You can override any configuration option when you run MOC using the # '-O' command line option: # # mocp -O AutoNext=no -O messagelingertime=1 -O XTerms+=xxt:xwt # # This command line option can be repeated as many times as needed and # the configuration option name is not case sensitive. (Note that MOC # does not perform variable substitution on the value of such overridden # configuration options.) Most option values are set before the # configuration file is processed (which allows the new values to be # picked up by substitutions), however list-valued options are overridden # afterwards (which gives the choice of whether the configured values are # replaced or added to). # Remember that the client and server are separate processes and the # server will retain the configuration values formed from the environment # within which it was originally started. # Show file titles (title, author, album) instead of file names? #ReadTags = yes # In which directory do you store your music files? If you specify it # you will be able to jump straight to this directory with the '-m' # parameter or the 'm' command. This can also point to a playlist. # # Example: MusicDir = "/home/joe/music" # #MusicDir = # Start in the music directory by default? If set to 'no', start # in the directory being viewed when the was client last active or, # as a last resort, the directory in which the client is being started. # A single directory on the command line takes precedence. #StartInMusicDir = no # The number of lines which are retained in an in-memory circular logging # buffer. A value of zero indicates that lines will be written directly # to the log file, otherwise the latest CircularLogSize lines are retained # in memory and not written to the log file until the MOC client or server # are shutdown. If the client or server terminates abnormally then the # log lines are lost. # # This option is intended to help identify problems which occur infrequently # and for which the amount of disk space consumed by logging would otherwise # be a limiting factor. Obviously the memory footprint will increase in # proportion to the value of this option. #CircularLogSize = 0 # How to sort? FileName is the option's only value for now. #Sort = FileName # Show errors in the streams (for example, broken frames in MP3 files)? #ShowStreamErrors = no # Ignore CRC errors in MP3 files? Most players do that, so the default # value is 'yes'. #MP3IgnoreCRCErrors = yes # Set playback toggles. #Repeat = no #Shuffle = no #AutoNext = yes # Default FormatString: # # %n - Track number # %a - Artist # %A - Album # %t - Title # %(X:TRUE:FALSE) - Ternary expression: if X exists, do TRUE, # otherwise FALSE. The escape character must # be doubled (i.e., '\\'). (See zshmisc # documentation for more information.) # #FormatString = "%(n:%n :)%(a:%a - :)%(t:%t:)%(A: \(%A\):)" # Input and output buffer sizes (in kilobytes). #InputBuffer = 512 # Minimum value is 32KB #OutputBuffer = 512 # Minimum value is 128KB # How much to fill the input buffer before playing (in kilobytes)? # This can't be greater than the value of InputBuffer. While this has # a positive effect for network streams, it also causes the broadcast # audio to be delayed. #Prebuffering = 64 # Use this HTTP proxy server for internet streams. If not set, the # environment variables http_proxy and ALL_PROXY will be used if present. # # Format: HTTPProxy = PROXY_NAME:PORT # #HTTPProxy = # Sound driver - OSS, ALSA, JACK, SNDIO (on OpenBSD) or null (only for # debugging). You can enter more than one driver as a colon-separated # list. The first working driver will be used. #SoundDriver = @SOUNDDRIVER@ # Jack output settings. #JackClientName = "moc" #JackStartServer = no #JackOutLeft = "system:playback_1" #JackOutRight = "system:playback_2" # OSS output settings. #OSSDevice = /dev/dsp #OSSMixerDevice = /dev/mixer #OSSMixerChannel1 = pcm # 'pcm', 'master' or 'speaker' #OSSMixerChannel2 = master # 'pcm', 'master' or 'speaker' # ALSA output settings. If you need to dump the audio produced by MOC # to a file for diagnostic purposes, the following setting of 'ALSADevice' # should do that: # # ALSADevice=tee:hw,'/tmp/out.wav',wav # #ALSADevice = default #ALSAMixer1 = PCM #ALSAMixer2 = Master # Under some circumstances on 32-bit systems, audio played continously # for long periods of time may begin to stutter. Setting this option to # 'yes' will force MOC to avoid ALSA's dmix resampling and prevent this # stutter. But it also has other implications: # # - You may experience unacceptably high CPU load. # - ALSA's resampler plug-ins will not be used. # - The resampling may be of lower quality than ALSA would provide. # - You may need to try different "ResampleMethod" option settings. # - The "ForceSampleRate" option may be ineffective. # - If libsamplerate is not configured, many audios may be unplayable. # #ALSAStutterDefeat = no # Save software mixer state? # If enabled, a file 'softmixer' will be created in '~/.moc/' storing the # mixersetting set when the server is shut down. # Note that there is a "hidden" 'Amplification' setting in that file. # Amplification (0-200) is used to scale the mixer setting (0-100). This # results in a higher signal amplitude but may also produce clipping. #Softmixer_SaveState = yes # Save equalizer state? # If enabled, a file 'equalizer' will be created in '~/.moc/' storing the # equalizer settings when the server is shut down. # Note that there is a "hidden" 'Mixin' setting in that file. # Mixin (0.0-1.0) is used to determine how much of the original signal is # used after equalizing. 0 means to only use the equalized sound, while 1 # effectively disabled the mixer. The default is 0.25. #Equalizer_SaveState = yes # Show files with dot at the beginning? #ShowHiddenFiles = no # Hide file name extensions? #HideFileExtension = no # Show file format in menu? #ShowFormat = yes # Show file time in menu? Possible values: 'yes', 'no' and 'IfAvailable' # (meaning show the time only when it is already known, which often works # faster). #ShowTime = IfAvailable # Show time played as a percentage in the time progress bar. #ShowTimePercent = no # Values of the TERM environment variable which are deemed to be managed by # screen(1). If you are setting a specific terminal using screen(1)'s # '-T ' option, then you will need to add 'screen.' to this list. # Note that this is only a partial test; the value of the WINDOW environment # variable must also be a number (which screen(1) sets). #ScreenTerms = screen:screen-w:vt100 # Values of the TERM environment variable which are deemed to be xterms. If # you are using MOC within screen(1) under an xterm, then add screen(1)'s # TERM setting here as well to cause MOC to update the xterm's title. #XTerms = xterm #XTerms += xterm-colour:xterm-color #XTerms += xterm-256colour:xterm-256color #XTerms += rxvt:rxvt-unicode #XTerms += rxvt-unicode-256colour:rxvt-unicode-256color #XTerms += eterm # Theme file to use. This can be absolute path or relative to # /usr/share/moc/themes/ (depends on installation prefix) or # ~/.moc/themes/ . # # Example: Theme = laras_theme # #Theme = # The theme used when running on an xterm. # # Example: XTermTheme = transparent-background # #XTermTheme = # Should MOC try to autoload the default lyrics file for an audio? (The # default lyrics file is a text file with the same file name as the audio # file name with any trailing "extension" removed.) #AutoLoadLyrics = yes # MOC directory (where pid file, socket and state files are stored). # You can use ~ at the beginning. #MOCDir = ~/.moc # Use mmap() to read files. mmap() is much slower on NFS. #UseMMap = no # Use MIME to identify audio files. This can make for slower loading # of playlists but is more accurate than using "extensions". #UseMimeMagic = no # Assume this encoding for ID3 version 1/1.1 tags (MP3 files). Unlike # ID3v2, UTF-8 is not used here and MOC can't guess how tags are encoded. # Another solution is using librcc (see the next option). This option is # ignored if UseRCC is set to 'yes'. #ID3v1TagsEncoding = WINDOWS-1250 # Use librcc to fix ID3 version 1/1.1 tags encoding. #UseRCC = yes # Use librcc to filenames and directory names encoding. #UseRCCForFilesystem = yes # When this option is set the player assumes that if the encoding of # ID3v2 is set to ISO-8859-1 then the ID3v1TagsEncoding is actually # that and applies appropriate conversion. #EnforceTagsEncoding = no # Enable the conversion of filenames from the local encoding to UTF-8. #FileNamesIconv = no # Enable the conversion of the xterm title from UTF-8 to the local encoding. #NonUTFXterm = no # Should MOC precache files to assist gapless playback? #Precache = yes # Remember the playlist after exit? #SavePlaylist = yes # When using more than one client (interface) at a time, do they share # the playlist? #SyncPlaylist = yes # Choose a keymap file (relative to '~/.moc/' or using an absolute path). # An annotated example keymap file is included ('keymap.example'). # # Example: Keymap = my_keymap # #Keymap = # Use ASCII rather than graphic characters for drawing lines. This # helps on some terminals. #ASCIILines = no # FastDirs, these allow you to jump directly to a directory, the key # bindings are in the keymap file. # # Examples: Fastdir1 = /mp3/rock # Fastdir2 = /mp3/electronic # Fastdir3 = /mp3/rap # Fastdir4 = /mp3/etc # #Fastdir1 = #Fastdir2 = #Fastdir3 = #Fastdir4 = #Fastdir5 = #Fastdir6 = #Fastdir7 = #Fastdir8 = #Fastdir9 = #Fastdir10 = # How fast to seek (in number of seconds per keystroke). The first # option is for normal seek and the second for silent seek. #SeekTime = 1 #SilentSeekTime = 5 # PreferredDecoders allows you to specify which decoder should be used # for any given audio format. It is a colon-separated list in which # each entry is of the general form 'code(decoders)', where 'code' # identifies the audio format and 'decoders' is a comma-separated list # of decoders in order of preference. # # The audio format identifier may be either a filename extension or a # MIME media type. If the latter, the format is 'type/subtype' (e.g., # 'audio/flac'). Because different systems may give different MIME # media types, any 'x-' prefix of the subtype is ignored both here and # in the actual file MIME type (so all combinations of 'audio/flac' and # 'audio/x-flac' match each other). # # For Internet streams the matching is done on MIME media type and on # actual content. For files the matches are made on MIME media type # (if the 'UseMimeMagic' option is set) and on filename extension. The # MIME media type of a file is not determined until the first entry for # MIME is encountered in the list. # # The matching is done in the order of appearance in the list with any # entries added from the command line being matched before those listed # here. Therefore, if you place all filename extension entries before # all MIME entries you will speed up MOC's processing of directories # (which could be significant for remote file systems). # # The decoder list may be empty, in which case no decoders will be used # for files (and files with that audio format ignored) while Internet # streams will be assessed on the actual content. Any decoder position # may contain an asterisk, in which case any decoder not otherwise listed # which can handle the audio format will be used. It is not an error to # list the same decoder twice, but neither does it make sense to do so. # # If you have a mix of audio and non-audio files in your directories, you # may wish to include entries at top of the list which ignore non-audio # files by extension. # # In summary, the PreferredDecoders option provides fine control over the # type of matching which is performed (filename extension, MIME media # type and streamed media content) and which decoder(s) (if any) are used # based on the option's list entries and their ordering. # # Examples: aac(aac,ffmpeg) first try FAAD2 for AACs then FFmpeg # mp3() ignore MP3 files # wav(*,sndfile) use sndfile for WAV as a last resort # ogg(vorbis,*):flac(flac,*) try Xiph decoders first # ogg():audio/ogg() ignore OGG files, and # force Internet selection by content # gz():html() ignore some non-audio files # # Any unspecified audio formats default to trying all decoders. # Any unknown (or misspelt) drivers are ignored. # All names are case insensitive. # The default setting reflects the historical situation modified by # the experience of users. # #PreferredDecoders = aac(aac,ffmpeg):m4a(ffmpeg) #PreferredDecoders += mpc(musepack,*,ffmpeg):mpc8(musepack,*,ffmpeg) #PreferredDecoders += sid(sidplay2):mus(sidplay2) #PreferredDecoders += wav(sndfile,*,ffmpeg) #PreferredDecoders += wv(wavpack,*,ffmpeg) #PreferredDecoders += audio/aac(aac):audio/aacp(aac):audio/m4a(ffmpeg) #PreferredDecoders += audio/wav(sndfile,*) # The following PreferredDecoders attempt to handle the ambiguity surrounding # container types such as OGG for files. The first two entries will force # a local file to the correct decoder (assuming the .ogg file contains Vorbis # audio), while the MIME media types will cause Internet audio streams to # be assessed on content (which may be either Vorbis or Speex). # #PreferredDecoders += ogg(vorbis,*,ffmpeg):oga(vorbis,*,ffmpeg):ogv(ffmpeg) #PreferredDecoders += application/ogg(vorbis):audio/ogg(vorbis) #PreferredDecoders += flac(flac,*,ffmpeg) #PreferredDecoders += opus(ffmpeg) #PreferredDecoders += spx(speex) # Which resampling method to use. There are a few methods of resampling # sound supported by libresamplerate. The default is 'Linear') which is # also the fastest. A better description can be found at: # # http://www.mega-nerd.com/libsamplerate/api_misc.html#Converters # # but briefly, the following methods are based on bandlimited interpolation # and are higher quality, but also slower: # # SincBestQuality - really slow (I know you probably have an xx GHz # processor, but it's still not enough to not see # this in the top output :) The worst case # Signal-to-Noise Ratio is 97dB. # SincMediumQuality - much faster. # SincFastest - the fastest bandlimited interpolation. # # And these are lower quality, but much faster methods: # # ZeroOrderHold - really poor quality, but it's really fast. # Linear - a bit better and a bit slower. # #ResampleMethod = Linear # Always use this sample rate (in Hz) when opening the audio device (and # resample the sound if necessary). When set to 0 the device is opened # with the file's rate. #ForceSampleRate = 0 # By default, even if the sound card reports that it can output 24bit samples # MOC converts 24bit PCM to 16bit. Setting this option to 'yes' allows MOC # to use 24bit output. (The MP3 decoder, for example, uses this format.) # This is disabled by default because there were reports that it prevents # MP3 files from playing on some soundcards. #Allow24bitOutput = no # Use realtime priority for output buffer thread. This will prevent gaps # while playing even with heavy load. The user who runs MOC must have # permissions to set such a priority. This could be dangerous, because it # is possible that a bug in MOC will freeze your computer. #UseRealtimePriority = no # The number of audio files for which MOC will cache tags. When this limit # is reached, file tags are discarded on a least recently used basis (with # one second resolution). You can disable the cache by giving it a size of # zero. Note that if you decrease the cache size below the number of items # currently in the cache, the number will not decrease immediately (if at # all). #TagsCacheSize = 256 # Number items in the playlist. #PlaylistNumbering = yes # Main window layouts can be configured. You can change the position and # size of the menus (directory and playlist). You have three layouts and # can switch between then using the 'l' key (standard mapping). By default, # only two layouts are configured. # # The format is as follows: # # - Each layout is described as a list of menu entries. # - Each menu entry is of the form: # # menu(position_x, position_y, width, height) # # where 'menu' is either 'directory' or 'playlist'. # - The parameters define position and size of the menu. They can # be absolute numbers (like 10) or a percentage of the screen size # (like 45%). # - 'width' and 'height' can have also value of 'FILL' which means # fill the screen from the menu's position to the border. # - Menus may overlap. # # You must describe at least one menu (default is to fill the whole window). # There must be at least one layout (Layout1) defined; others can be empty. # # Example: Layout1 = playlist(50%,50%,50%,50%) # Layout2 = "" # Layout3 = "" # # Just one layout, the directory will occupy the whole # screen, the playlist will have 1/4 of the screen size # and be positioned at lower right corner. (Note that # because the playlist will be hidden by the directory # you will have to use the TAB key to make the playlist # visible.) # # Example: Layout1 = playlist(0,0,100%,10):directory(0,10,100%,FILL) # # The screen is split into two parts: playlist at the top # and the directory menu at the bottom. Playlist will # occupy 10 lines and the directory menu the rest. # #Layout1 = directory(0,0,50%,100%):playlist(50%,0,FILL,100%) #Layout2 = directory(0,0,100%,100%):playlist(0,0,100%,100%) #Layout3 = "" # When the song changes, should the menu be scrolled so that the currently # played file is visible? #FollowPlayedFile = yes # What to do if the interface was started and the server is already playing # something from the playlist? If CanStartInPlaylist is set to 'yes', the # interface will switch to the playlist. When set to 'no' it will start # from the last directory. #CanStartInPlaylist = yes # Executing external commands (1 - 10) invoked with key commands (F1 - F10 # by default). # # Some arguments are substituted before executing: # # %f - file path # %i - title made from tags # %S - start block mark (in seconds) # %E - end block mark (in seconds) # # Data from tags can also be substituted: # # %t - title # %a - album # %r - artist # %n - track # %m - time of the file (in seconds) # # The parameters above apply to the currently selected file. If you change # them to capital letters, they are taken from the file currently playing. # # Programs are run using execv(), not a shell, so you can't do things like # redirecting the output to a file. The command string is split using blank # characters as separators; the first element is the command to be executed # and the rest are its parameters, so if you use "echo Playing: %I" we run # program 'echo' (from $PATH) with 2 parameters: the string 'Playing:' and # the title of the file currently playing. Even if the title contains # spaces, it's still one parameter and it's safe if it contains `rm -rf /`. # # Examples: ExecCommand1 = "cp %f /mnt/usb_drive" # ExecCommand2 = "/home/joe/now_playing %I" # #ExecCommand1 = #ExecCommand2 = #ExecCommand3 = #ExecCommand4 = #ExecCommand5 = #ExecCommand6 = #ExecCommand7 = #ExecCommand8 = #ExecCommand9 = #ExecCommand10 = # Display the cursor in the line with the selected file. Some braille # readers (the Handy Tech modular series ZMU 737, for example) use the # cursor to focus and can make use of it to present the file line even # when other fields are changing. #UseCursorSelection = no # Set the terminal title when running under xterm. #SetXtermTitle = yes # Set the terminal title when running under screen(1). If MOC can detect # that it is running under screen(1), then it will set an appropriate # title (see description of ScreenTerms above). However, if multiple # levels of screen management are involved, detection might fail and this # could cause a screen upset. In that situation you can use this option # to force screen titles off. #SetScreenTitle = yes # Display full paths instead of just file names in the playlist. #PlaylistFullPaths = yes # The following setting describes how block markers are displayed in # the play time progress bar. Its value is a string of exactly three # characters. The first character is displayed in a position which # corresponds to the time marked as the start of a block and the last # character to the time marked as the end of the block. The middle # character is displayed instead if both the start and the end of the block # would fall in the same position (within the resolution of the interface). # You can turn off the displaying of these block marker positions by using # three space characters. #BlockDecorators = "`\"'" # How long (in seconds) to leave a message displayed on the screen. # Setting this to a high value allows you to scroll through the messages # using the 'hide_message' key. Setting it to zero means you'll have to # be quick to see any message at all. Any new messages will be queued up # and displayed after the current message's linger time expires. #MessageLingerTime = 3 # Does MOC display a prefix on delayed messages indicating # the number of queued messages still to be displayed? #PrefixQueuedMessages = yes # String to append to the queued message count if any # error messages are still waiting to be displayed. #ErrorMessagesQueued = "!" # Self-describing ModPlug options (with 'yes' or 'no' values). #ModPlug_Oversampling = yes #ModPlug_NoiseReduction = yes #ModPlug_Reverb = no #ModPlug_MegaBass = no #ModPlug_Surround = no # ModPlug resampling mode. # Valid values are: # # FIR - 8 tap fir filter (extremely high quality) # SPLINE - Cubic spline interpolation (high quality) # LINEAR - Linear interpolation (fast, good quality) # NEAREST - No interpolation (very fast, extremely bad sound quality) # #ModPlug_ResamplingMode = FIR # Other self-describing ModPlug audio characteristic options. # (Note that the 32 bit sample size seems to be buggy.) #ModPlug_Channels = 2 # 1 or 2 channels #ModPlug_Bits = 16 # 8, 16 or 32 bits #ModPlug_Frequency = 44100 # 11025, 22050, 44100 or 48000 Hz #ModPlug_ReverbDepth = 0 # 0 (quiet) to 100 (loud) #ModPlug_ReverbDelay = 0 # Delay in ms (usually 40-200ms) #ModPlug_BassAmount = 0 # 0 (quiet) to 100 (loud). #ModPlug_BassRange = 10 # Cutoff in Hz (10-100). #ModPlug_SurroundDepth = 0 # Surround level 0(quiet)-100(heavy). #ModPlug_SurroundDelay = 0 # Surround delay in ms, usually 5-40ms. #ModPlug_LoopCount = 0 # 0 (never), n (times) or -1 (forever) # Self-describing TiMidity audio characteristic options. #TiMidity_Rate = 44100 # Between 8000 and 48000 #TiMidity_Bits = 16 # 8 or 16 #TiMidity_Channels = 2 # 1 or 2 #TiMidity_Volume = 100 # 0 to 800 # You can setup a TiMidity-Config-File here. # Leave it unset to use library defaults (/etc/timidity.cfg mostly). # Setting it to 'yes' also uses the library defaults. # Set it to 'no' if you don't have any configuration file. # Otherwise set it to the name of a specific file. #TiMidity_Config = # Self-describing SidPlay2 audio characteristic options. #SidPlay2_DefaultSongLength = 180 # If not in database (in seconds) #SidPlay2_MinimumSongLength = 0 # Play at least n (in seconds) #SidPlay2_Frequency = 44100 # 4000 to 48000 #SidPlay2_Bits = 16 # 8 or 16 #SidPlay2_Optimisation = 0 # 0 (worst quality) to 2 (best quality) # Set path to a HVSC-compatible database (if not set, database is disabled). #SidPlay2_Database = # SidPlay2 playback Mode: # # "M": Mono (best for many SIDs) # "S": Stereo # "L"/"R": Left / Right # #SidPlay2_PlayMode = "M" # Use start-song information from SID ('yes') or start at first song # ('no'). Songs before the start-song won't be played. #SidPlay2_StartAtStart = yes # Play sub-tunes. #SidPlay2_PlaySubTunes = yes # Run the OnSongChange command when a new song starts playing. # Specify the full path (i.e. no leading '~') of an executable to run. # Arguments will be passed, and you can use the following escapes: # # %a artist # %r album # %f filename # %t title # %n track # %d file duration in XX:YY form # %D file duration, number of seconds # # No pipes/redirects can be used directly, but writing a shell script # can do the job. # # Example: OnSongChange = "/home/jack/.moc/myscript %a %r" # #OnSongChange = # If RepeatSongChange is 'yes' then MOC will execute the command every time # a song starts playing regardless of whether or not it is just repeating. # Otherwise the command will only be executed when a different song is # started. #RepeatSongChange = no # Run the OnStop command (full path, no arguments) when MOC changes state # to stopped (i.e., when user stopped playing or changes a song). # # Example: OnStop = "/home/jack/.moc/myscript_on_stop" # #OnStop = # Run the OnServerStart or OnServerStop commands (full path, no arguments) # when MOC server is started or terminated respectively. The server will # not wait for the commands to complete before continuing. #OnServerStart = #OnServerStop = # This option determines which song to play after finishing all the songs # in the queue. Setting this to 'yes' causes MOC to play the song which # follows the song being played before queue playing started. If set to # 'no', MOC will play the song following the last song in the queue if it # is in the playlist. The default is 'yes' because this is the way other # players usually behave. (Note that this option previously took the # values 1 and 0; these are now deprecated in favour of 'yes' and 'no'.) #QueueNextSongReturn = yes moc-2.6.0~svn-r3005/config.rpath0000664000076400000500000004401211742710566015446 0ustar riesebiesrc#! /bin/sh # Output a system dependent set of variables, describing how to set the # run time search path of shared libraries in an executable. # # Copyright 1996-2011 Free Software Foundation, Inc. # Taken from GNU libtool, 2001 # Originally by Gordon Matzigkeit , 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # The first argument passed to this file is the canonical host specification, # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld # should be set by the caller. # # The set of defined variables is at the end of this script. # Known limitations: # - On IRIX 6.5 with CC="cc", the run time search patch must not be longer # than 256 bytes, otherwise the compiler driver will dump core. The only # known workaround is to choose shorter directory names for the build # directory and/or the installation directory. # All known linkers require a `.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a shrext=.so host="$1" host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # Code taken from libtool.m4's _LT_CC_BASENAME. for cc_temp in $CC""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` # Code taken from libtool.m4's _LT_COMPILER_PIC. wl= if test "$GCC" = yes; then wl='-Wl,' else case "$host_os" in aix*) wl='-Wl,' ;; darwin*) case $cc_basename in xlc*) wl='-Wl,' ;; esac ;; mingw* | cygwin* | pw32* | os2* | cegcc*) ;; hpux9* | hpux10* | hpux11*) wl='-Wl,' ;; irix5* | irix6* | nonstopux*) wl='-Wl,' ;; newsos6) ;; linux* | k*bsd*-gnu) case $cc_basename in ecc*) wl='-Wl,' ;; icc* | ifort*) wl='-Wl,' ;; lf95*) wl='-Wl,' ;; pgcc | pgf77 | pgf90) wl='-Wl,' ;; ccc*) wl='-Wl,' ;; como) wl='-lopt=' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) wl='-Wl,' ;; esac ;; esac ;; osf3* | osf4* | osf5*) wl='-Wl,' ;; rdos*) ;; solaris*) wl='-Wl,' ;; sunos4*) wl='-Qoption ld ' ;; sysv4 | sysv4.2uw2* | sysv4.3*) wl='-Wl,' ;; sysv4*MP*) ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) wl='-Wl,' ;; unicos*) wl='-Wl,' ;; uts4*) ;; esac fi # Code taken from libtool.m4's _LT_LINKER_SHLIBS. hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_direct=no hardcode_minus_L=no case "$host_os" in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test "$GCC" != yes; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd*) with_gnu_ld=no ;; esac ld_shlibs=yes if test "$with_gnu_ld" = yes; then # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. # Unlike libtool, we use -rpath here, not --rpath, since the documented # option of GNU ld is called -rpath, not --rpath. hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' case "$host_os" in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test "$host_cpu" != ia64; then ld_shlibs=no fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # Samuel A. Falvo II reports # that the semantics of dynamic libraries on AmigaOS, at least up # to version 4, is to share data among multiple programs linked # with the same dynamic library. Since this doesn't match the # behavior of shared libraries on other platforms, we cannot use # them. ld_shlibs=no ;; beos*) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then : else ld_shlibs=no fi ;; interix[3-9]*) hardcode_direct=no hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; gnu* | linux* | k*bsd*-gnu) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; netbsd*) ;; solaris*) if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then ld_shlibs=no elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' else ld_shlibs=no fi ;; esac ;; sunos4*) hardcode_direct=yes ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; esac if test "$ld_shlibs" = no; then hardcode_libdir_flag_spec= fi else case "$host_os" in aix3*) # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test "$GCC" = yes; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test "$host_cpu" = ia64; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # need to do runtime linking. case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then aix_use_runtimelinking=yes break fi done ;; esac fi hardcode_direct=yes hardcode_libdir_separator=':' if test "$GCC" = yes; then case $host_os in aix4.[012]|aix4.[012].*) collect2name=`${CC} -print-prog-name=collect2` if test -f "$collect2name" && \ strings "$collect2name" | grep resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac fi # Begin _LT_AC_SYS_LIBPATH_AIX. echo 'int main () { return 0; }' > conftest.c ${CC} ${LDFLAGS} conftest.c -o conftest aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` fi if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib" fi rm -f conftest.c conftest # End _LT_AC_SYS_LIBPATH_AIX. if test "$aix_use_runtimelinking" = yes; then hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" else if test "$host_cpu" = ia64; then hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' else hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" fi fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # see comment about different semantics on the GNU ld section ld_shlibs=no ;; bsdi[45]*) ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec=' ' libext=lib ;; darwin* | rhapsody*) hardcode_direct=no if test "$GCC" = yes ; then : else case $cc_basename in xlc*) ;; *) ld_shlibs=no ;; esac fi ;; dgux*) hardcode_libdir_flag_spec='-L$libdir' ;; freebsd1*) ld_shlibs=no ;; freebsd2.2*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; freebsd2*) hardcode_direct=yes hardcode_minus_L=yes ;; freebsd* | dragonfly*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; hpux9*) hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; hpux10*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no ;; *) hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; netbsd*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; newsos6) hardcode_direct=yes hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then hardcode_libdir_flag_spec='${wl}-rpath,$libdir' else case "$host_os" in openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) hardcode_libdir_flag_spec='-R$libdir' ;; *) hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; esac fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; osf3*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) if test "$GCC" = yes; then hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' else # Both cc and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi hardcode_libdir_separator=: ;; solaris*) hardcode_libdir_flag_spec='-R$libdir' ;; sunos4*) hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes ;; sysv4) case $host_vendor in sni) hardcode_direct=yes # is this really true??? ;; siemens) hardcode_direct=no ;; motorola) hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac ;; sysv4.3*) ;; sysv4*MP*) if test -d /usr/nec; then ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) ;; sysv5* | sco3.2v5* | sco5v6*) hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' hardcode_libdir_separator=':' ;; uts4*) hardcode_libdir_flag_spec='-L$libdir' ;; *) ld_shlibs=no ;; esac fi # Check dynamic linker characteristics # Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. # Unlike libtool.m4, here we don't care about _all_ names of the library, but # only about the one the linker finds when passed -lNAME. This is the last # element of library_names_spec in libtool.m4, or possibly two of them if the # linker has special search rules. library_names_spec= # the last element of library_names_spec in libtool.m4 libname_spec='lib$name' case "$host_os" in aix3*) library_names_spec='$libname.a' ;; aix[4-9]*) library_names_spec='$libname$shrext' ;; amigaos*) library_names_spec='$libname.a' ;; beos*) library_names_spec='$libname$shrext' ;; bsdi[45]*) library_names_spec='$libname$shrext' ;; cygwin* | mingw* | pw32* | cegcc*) shrext=.dll library_names_spec='$libname.dll.a $libname.lib' ;; darwin* | rhapsody*) shrext=.dylib library_names_spec='$libname$shrext' ;; dgux*) library_names_spec='$libname$shrext' ;; freebsd1*) ;; freebsd* | dragonfly*) case "$host_os" in freebsd[123]*) library_names_spec='$libname$shrext$versuffix' ;; *) library_names_spec='$libname$shrext' ;; esac ;; gnu*) library_names_spec='$libname$shrext' ;; hpux9* | hpux10* | hpux11*) case $host_cpu in ia64*) shrext=.so ;; hppa*64*) shrext=.sl ;; *) shrext=.sl ;; esac library_names_spec='$libname$shrext' ;; interix[3-9]*) library_names_spec='$libname$shrext' ;; irix5* | irix6* | nonstopux*) library_names_spec='$libname$shrext' case "$host_os" in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; *) libsuff= shlibsuff= ;; esac ;; esac ;; linux*oldld* | linux*aout* | linux*coff*) ;; linux* | k*bsd*-gnu) library_names_spec='$libname$shrext' ;; knetbsd*-gnu) library_names_spec='$libname$shrext' ;; netbsd*) library_names_spec='$libname$shrext' ;; newsos6) library_names_spec='$libname$shrext' ;; nto-qnx*) library_names_spec='$libname$shrext' ;; openbsd*) library_names_spec='$libname$shrext$versuffix' ;; os2*) libname_spec='$name' shrext=.dll library_names_spec='$libname.a' ;; osf3* | osf4* | osf5*) library_names_spec='$libname$shrext' ;; rdos*) ;; solaris*) library_names_spec='$libname$shrext' ;; sunos4*) library_names_spec='$libname$shrext$versuffix' ;; sysv4 | sysv4.3*) library_names_spec='$libname$shrext' ;; sysv4*MP*) library_names_spec='$libname$shrext' ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) library_names_spec='$libname$shrext' ;; uts4*) library_names_spec='$libname$shrext' ;; esac sed_quote_subst='s/\(["`$\\]\)/\\\1/g' escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` shlibext=`echo "$shrext" | sed -e 's,^\.,,'` escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' < #ifdef _POSIX_VERSION #if _POSIX_VERSION >= 200112L posix_2001_compatible #endif #endif ], [true], [AC_MSG_ERROR([A POSIX.1-2001 compatible system is required.])]) AC_PROG_AWK AC_LIBTOOL_DLOPEN AC_DISABLE_STATIC AC_ENABLE_SHARED AC_PROG_LIBTOOL AC_LIB_LTDL AC_SUBST([EXTRA_OBJS]) plugindir=$libdir/moc AC_SUBST([plugindir]) PLUGIN_LDFLAGS='-module -avoid-version' AC_SUBST([PLUGIN_LDFLAGS]) case "$host_os" in linux*) AC_DEFINE([LINUX], 1, [Define if your system is GNU/Linux]) ;; openbsd*) AC_DEFINE([OPENBSD], 1, [Define if your system is OpenBSD]) ;; freebsd*) AC_DEFINE([FREEBSD], 1, [Define if your system is FreeBSD]) ;; esac dnl Check that a function pointer is the same size as a data pointer. AC_MSG_CHECKING([if function and data pointer sizes match]) AC_RUN_IFELSE( [AC_LANG_PROGRAM( [], [[if (sizeof(void *) != sizeof(void (*)())) return 1;]] )], [AC_MSG_RESULT([yes])], [WARN_POINTER_MISMATCH=yes AC_MSG_RESULT([no])], [AC_MSG_RESULT([unknown]) AC_MSG_WARN([Cross-compilation means pointer size test couldn't be run])]) AC_DEFINE([_FILE_OFFSET_BITS], 64, [Use 64bit IO]) dnl required X/Open SUS standard headers AC_CHECK_HEADERS([strings.h sys/un.h],, AC_MSG_ERROR([Required X/Open SUS header files are not present.])) dnl required OS provided headers AC_CHECK_HEADERS([sys/ioctl.h sys/param.h],, AC_MSG_ERROR([Required OS header files are not present.])) dnl optional headers AC_CHECK_HEADERS([byteswap.h]) dnl langinfo AC_CHECK_HEADERS([langinfo.h]) AC_CHECK_HEADERS([nl_types.h]) AC_CHECK_FUNCS([nl_langinfo]) dnl CODESET (taken from vim) AC_MSG_CHECKING(for nl_langinfo(CODESET)) AC_TRY_LINK([ #ifdef HAVE_LANGINFO_H # include #endif ], [char *cs = nl_langinfo(CODESET);], AC_MSG_RESULT(yes) AC_DEFINE([HAVE_NL_LANGINFO_CODESET], 1, [Define if you have CODESET constant]), AC_MSG_RESULT(no)) AC_C_BIGENDIAN AC_C_CONST AC_TYPE_INTPTR_T AX_CFLAGS_GCC_OPTION(-Wall) AX_CFLAGS_GCC_OPTION(-Wextra) dnl Overly-enthusiastic warning suppression. save_CFLAGS="$CFLAGS" AX_CFLAGS_GCC_OPTION([-Wgnu-zero-variadic-macro-arguments], , AC_DEFINE([HAVE_VARIADIC_MACRO_WARNING], 1, [Define if compiler recognises warning option])) AX_CFLAGS_GCC_OPTION([-Werror=unknown-warning-option]) AX_CFLAGS_GCC_OPTION([-Wformat-truncation], , AC_DEFINE([HAVE_FORMAT_TRUNCATION_WARNING], 1, [Define if compiler recognises warning option])) CFLAGS="$save_CFLAGS" PKG_PROG_PKG_CONFIG([0.20]) if test "x$PKG_CONFIG" = "x" then AC_MSG_WARN([No pkg-config utility found or it's too old, I will have trouble finding installed libraries.]) fi AC_ARG_ENABLE(cache, AS_HELP_STRING([--enable-cache], [Enable tags caching code])) if test "x$enable_cache" != "xno" then save_CFLAGS="$CFLAGS" CFLAGS= AX_PATH_BDB([4.1], [], AC_MSG_ERROR([BerkeleyDB (libdb) not found.])) CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" CFLAGS="$save_CFLAGS" LDFLAGS="$LDFLAGS $BDB_LDFLAGS" EXTRA_LIBS="$EXTRA_LIBS $BDB_LIBS" AC_CHECK_TYPES([u_int], , , [[#include "${srcdir}/compiler.h"] [#include ] [#include ]]) fi AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss], [Compile without OSS support])) if test "x$with_oss" != "xno" then OSSLIBDIR="$with_oss" if test "x$with_oss" = "x" || test "x$with_oss" = "xyes" then OSSLIBDIR="/usr/lib/oss" if test -f "/etc/oss.conf" then . /etc/oss.conf fi fi if test -d "$OSSLIBDIR/include" then OSS_CFLAGS="-I$OSSLIBDIR/include" fi save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $OSS_CFLAGS" AC_CHECK_HEADERS([sys/soundcard.h soundcard.h]) CPPFLAGS="$save_CPPFLAGS" if test "$ac_cv_header_sys_soundcard_h" = "yes" -o \ "$ac_cv_header_soundcard_h" = "yes" then AC_DEFINE([HAVE_OSS], 1, [Define if you have OSS.]) EXTRA_OBJS="$EXTRA_OBJS oss.o" CFLAGS="$CFLAGS $OSS_CFLAGS" SOUND_DRIVERS="$SOUND_DRIVERS OSS" AC_CHECK_LIB([ossaudio], [_oss_ioctl], [EXTRA_LIBS="$EXTRA_LIBS -lossaudio"]) fi fi AC_ARG_WITH(sndio, AS_HELP_STRING([--without-sndio], [Compile without SNDIO support])) if test "x$with_sndio" != "xno" then AC_CHECK_HEADERS([sndio.h]) if test "$ac_cv_header_sndio_h" = "yes" then AC_DEFINE([HAVE_SNDIO], 1, [Define if you have SNDIO.]) EXTRA_OBJS="$EXTRA_OBJS sndio_out.o" SOUND_DRIVERS="$SOUND_DRIVERS SNDIO" AC_CHECK_LIB([sndio], [sio_open], [EXTRA_LIBS="$EXTRA_LIBS -lsndio"]) fi fi AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa], [Compile without ALSA support])) COMPILE_ALSA="no" if test "x$with_alsa" != "xno" then PKG_CHECK_MODULES(ALSA, [alsa >= 1.0.11], [SOUND_DRIVERS="$SOUND_DRIVERS ALSA" EXTRA_OBJS="$EXTRA_OBJS alsa.o" AC_DEFINE([HAVE_ALSA], 1, [Define if you have ALSA.]) EXTRA_LIBS="$EXTRA_LIBS $ALSA_LIBS" COMPILE_ALSA="yes" CFLAGS="$CFLAGS $ALSA_CFLAGS"], [true]) fi AC_ARG_WITH(jack, AS_HELP_STRING([--without-jack], [Compile without JACK support])) if test "x$with_jack" != "xno" then PKG_CHECK_MODULES(JACK, [jack >= 0.4], [SOUND_DRIVERS="$SOUND_DRIVERS JACK" EXTRA_OBJS="$EXTRA_OBJS jack.o" AC_DEFINE([HAVE_JACK], 1, [Define if you have JACK.]) EXTRA_LIBS="$EXTRA_LIBS $JACK_LIBS" CFLAGS="$CFLAGS $JACK_CFLAGS" AC_SEARCH_LIBS(jack_client_open, jack, [AC_DEFINE([HAVE_JACK_CLIENT_OPEN], 1, [Define to 1 if you have the `jack_client_open' function.])])], [true]) fi AC_SUBST([SOUNDDRIVER]) case "$host_os" in openbsd*) SOUNDDRIVER="SNDIO:JACK:OSS";; *) SOUNDDRIVER="JACK:ALSA:OSS";; esac AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [Enable debugging code])) if test "x$enable_debug" = "xno" then AC_DEFINE([NDEBUG], 1, [Define if you don't want debugging code]) COMPILE_DEBUG='no' else if test "x$enable_debug" = "xgdb" then AX_CFLAGS_GCC_OPTION([-ggdb]) AX_CFLAGS_GCC_OPTION([-O0]) COMPILE_DEBUG='gdb' fi if test "x$ac_cv_cflags_gcc_option__ggdb" = "x" then AX_CFLAGS_GCC_OPTION([-g]) COMPILE_DEBUG='yes' fi EXTRA_OBJS="$EXTRA_OBJS null_out.o md5.o" fi AC_FUNC_MALLOC dnl required POSIX (TMR/TSF/XSI) functions AC_CHECK_FUNCS([localtime_r nanosleep strcasecmp \ strdup strncasecmp wcswidth],, AC_MSG_ERROR([Required POSIX functions are not present.])) dnl optional functions AC_FUNC_STRERROR_R AC_CHECK_FUNCS([sched_get_priority_max syslog]) dnl OSX / MacOS doesn't provide clock_gettime(3) prior to darwin-16.0.0 dnl so fall back to gettimeofday(2). case "$host_os" in [*darwin[1-9].*] | [*darwin1[0-5].*]) HAVE_CLOCK_GETTIME="no" ;; *darwin*) HAVE_CLOCK_GETTIME="yes" ;; *) AC_CHECK_LIB([rt], [clock_gettime], [HAVE_CLOCK_GETTIME="yes"], [HAVE_CLOCK_GETTIME="no"]) ;; esac if test "$HAVE_CLOCK_GETTIME" = "yes" then AC_DEFINE([HAVE_CLOCK_GETTIME], 1, [Define if you have clock_gettime(3)]) else AC_CHECK_FUNCS([gettimeofday], [AC_MSG_WARN([Using the obsolete gettimeofday(2) function.])], [AC_MSG_ERROR([No suitable current time function found.])]) fi dnl strcasestr(3) is a GNU extension AC_CHECK_DECLS([strcasestr], , , [[#include "${srcdir}/compiler.h"] [#include ]]) AC_CHECK_FUNCS([strcasestr]) dnl MIME magic AC_ARG_WITH(magic, AS_HELP_STRING([--without-magic], [Compile without MIME magic support])) COMPILE_MAGIC="no" if test "x$with_magic" != "xno" then AC_CHECK_LIB(magic, magic_open, [COMPILE_MAGIC="yes" AC_DEFINE([HAVE_LIBMAGIC], 1, [Define if you have libmagic.]) EXTRA_LIBS="$EXTRA_LIBS -lmagic"]) fi AC_FUNC_MMAP dnl POSIX threads AX_PTHREAD if test "$ax_pthread_ok" != "yes" then AC_MSG_ERROR([[I don't know how to compile pthreads code on this system.]]) fi CC="$PTHREAD_CC" CFLAGS="$PTHREAD_CFLAGS $CFLAGS" EXTRA_LIBS="$EXTRA_LIBS $PTHREAD_LIBS" AC_CHECK_FUNCS([getrlimit]) AC_CHECK_LIB([pthread], [pthread_attr_getstacksize], [AC_DEFINE([HAVE_PTHREAD_ATTR_GETSTACKSIZE], 1, [Define if you have pthread_attr_getstacksize(3).])]) dnl __attribute__ AX_C___ATTRIBUTE__ if test "x$ax_cv___attribute__" = "xyes" then AX_GCC_FUNC_ATTRIBUTE(format) AX_GCC_FUNC_ATTRIBUTE(noreturn) AX_GCC_VAR_ATTRIBUTE(aligned) AX_GCC_VAR_ATTRIBUTE(unused) fi dnl popt AC_SEARCH_LIBS([poptGetContext], [popt], , AC_MSG_ERROR([POPT (libpopt) not found.])) dnl ncurses dnl This 'unset' circumvents a notified bug in AX_WITH_CURSES when dnl _PKG_CONFIG succeeds but a stale 'pkg_failed' value is checked. dnl It can be removed once an updated 'ax_with_curses.m4' is committed. unset pkg_failed AX_WITH_CURSES if test "x$ax_cv_curses" != "xyes" then AC_MSG_ERROR([You need curses/ncurses library and header files.]) fi EXTRA_LIBS="$EXTRA_LIBS $CURSES_LIBS" dnl Remove -D_GNU_SOURCE as it causes problems when defined mid-configure. for flag in $CURSES_CFLAGS do if test "x$flag" != "x-D_GNU_SOURCE" then CPPFLAGS="$CPPFLAGS $flag" fi done AC_RUN_LOG([: The test below is actually for set_escdelay in curses library.]) AC_RUN_LOG([: Using 'm' avoids unpacking CURSES_LIBS.]) AC_CHECK_LIB([m], [set_escdelay], AC_DEFINE([HAVE_SET_ESCDELAY], 1, [Define if you have set_escdelay.]), [], [$CURSES_LIBS]) dnl samplerate AC_ARG_WITH(samplerate, AS_HELP_STRING([--without-samplerate], [Compile without libsamplerate])) COMPILE_SAMPLERATE="no" if test "x$with_samplerate" != "xno" then PKG_CHECK_MODULES(samplerate, samplerate >= 0.1.0, [EXTRA_LIBS="$EXTRA_LIBS $samplerate_LIBS" CFLAGS="$CFLAGS $samplerate_CFLAGS" AC_DEFINE([HAVE_SAMPLERATE], 1, [Define if you have libsamplerate]) COMPILE_SAMPLERATE="yes"], [true]) fi dnl Decoder plugins m4_include(decoder_plugins/decoders.m4) dnl Require with iconv for charset translation. AM_ICONV if test "x$am_cv_func_iconv" != xyes; then AC_MSG_ERROR([No iconv library found.]) fi EXTRA_LIBS="$EXTRA_LIBS $LIBICONV" dnl librcc COMPILE_RCC=no AC_ARG_WITH(rcc, AS_HELP_STRING([--without-rcc], [Compile without LIBRCC support])) if test "x$with_rcc" != "xno" then AC_CHECK_HEADERS([librcc.h], [AC_DEFINE([HAVE_RCC], 1, [Define if you have librcc.h]) AC_CHECK_LIB(rcc, rccInit, [RCC_LIBS="-lrcc" AC_SUBST([RCC_LIBS]) COMPILE_RCC=yes]) ]) if ! $PKG_CONFIG --atleast-version 0.2.10 librcc then UPGRADE_LIBRCC="yes" fi fi dnl curl COMPILE_CURL="no" AC_ARG_WITH(curl, AS_HELP_STRING([--without-curl], [Compile without Network streams support])) if test "x$with_curl" != "xno" then PKG_CHECK_MODULES(CURL, [libcurl >= 7.15.1], [EXTRA_OBJS="$EXTRA_OBJS io_curl.o" AC_DEFINE([HAVE_CURL], 1, [Define if you have libcurl]) EXTRA_LIBS="$EXTRA_LIBS $CURL_LIBS" CFLAGS="$CFLAGS $CURL_CFLAGS" COMPILE_CURL="yes" if ! $PKG_CONFIG --atleast-version 7.15.4 libcurl then UPGRADE_LIBCURL="yes" fi], [true]) fi dnl Capture configuration options for this build. AC_DEFINE_UNQUOTED([CONFIGURATION], ["$ac_configure_args"], [Define to the configuration used to build MOC.]) dnl Capture SVN revision number for this build. AC_PATH_PROG(SVNVERSION, [svnversion]) if test -n "$SVNVERSION" then SVNREVN=`$SVNVERSION -n $srcdir` SVNREVN=`expr "$SVNREVN" : '\([[^:]]*\)'` if test "x$SVNREVN" = "xexported" then unset SVNREVN else echo -n $SVNREVN > REVISION EXTRA_DISTS="$EXTRA_DISTS REVISION" fi fi if test -z "$SVNREVN" && test -f $srcdir/REVISION then SVNREVN=`cat $srcdir/REVISION` EXTRA_DISTS="$EXTRA_DISTS REVISION" fi if test -n "$SVNREVN" then AC_DEFINE_UNQUOTED([PACKAGE_REVISION], ["$SVNREVN"], [The SVN revision of this build.]) fi AC_SUBST(EXTRA_LIBS) AC_SUBST(EXTRA_DISTS) AH_BOTTOM([#include "compiler.h"]) AC_OUTPUT([Makefile themes/Makefile config.example ]) echo echo "-----------------------------------------------------------------------" echo "MOC will be compiled with:" echo if test ${#DECODER_PLUGINS} -le 50 then echo "Decoder plugins: $DECODER_PLUGINS" else DECODERS_IX=`echo $DECODER_PLUGINS | $AWK '{match(substr($0, 1, 51), /.* /);print(RLENGTH)}'` DECODERS_1=`echo $DECODER_PLUGINS | $AWK "{print(substr(\\$0, 1, $DECODERS_IX - 1))}"` DECODERS_2=`echo $DECODER_PLUGINS | $AWK "{print(substr(\\$0, $DECODERS_IX + 1))}"` echo "Decoder plugins: $DECODERS_1" echo " $DECODERS_2" fi echo "Sound Drivers: "$SOUND_DRIVERS echo "DEBUG: "$COMPILE_DEBUG echo "RCC: "$COMPILE_RCC echo "Network streams: "$COMPILE_CURL echo "Resampling: "$COMPILE_SAMPLERATE echo "MIME magic: "$COMPILE_MAGIC echo "-----------------------------------------------------------------------" echo if test "x$UPGRADE_TAGLIB" = "xyes" then echo "WARNING: MOC will soon require TagLib version 1.5 or later;" echo " plan to upgrade your TagLib soon." echo fi if test "x$UPGRADE_MUSEPACK" = "xyes" then echo "WARNING: MOC will soon require Musepack libmpc (rather than libmpcdec);" echo " plan to upgrade your Musepack soon." echo fi if test "x$UPGRADE_LIBRCC" = "xyes" then echo "WARNING: MOC will soon require librcc version 0.2.10 or later;" echo " plan to upgrade your RCC library soon." echo fi if test "x$UPGRADE_LIBCURL" = "xyes" then echo "WARNING: MOC will soon require libcurl version 7.15.4 or later;" echo " plan to upgrade your cURL library soon." echo fi if test "x$DECODER_PLUGINS" = "x" then echo "WARNING: No decoder plugins are to be compiled;" echo " you will have to provide them separately." echo fi if test "x$SOUND_DRIVERS" = "x" then echo "WARNING: No sound output methods are to be compiled;" echo " you will not hear any sound!" echo fi if test "x$cross_compiling" = "xyes" then echo "WARNING: Because of cross-compilation, some tests could not be run" echo " and MOC may fail with memory access or illegal instruction" echo " errors when loading plug-ins." echo fi if test "x$WARN_POINTER_MISMATCH" = "xyes" then echo "WARNING: Because the sizes of data and function pointers are not" echo " the same on this architecture, MOC may fail with memory" echo " access or illegal instruction errors when loading plug-ins." echo fi if test "x$COMPILE_SAMPLERATE" = "xno" -a "x$COMPILE_ALSA" = "xyes" then echo "WARNING: Without libsamplerate, the 'ALSAStutterDefeat' option" echo " may severely restrict the number of audios playable." echo fi moc-2.6.0~svn-r3005/decoder.c0000664000076400000500000004421413327162643014714 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include "common.h" #include "decoder.h" #include "files.h" #include "log.h" #include "io.h" #include "options.h" static struct plugin { char *name; lt_dlhandle handle; struct decoder *decoder; } plugins[16]; #define PLUGINS_NUM (ARRAY_SIZE(plugins)) static int plugins_num = 0; static bool have_tremor = false; /* This structure holds the user's decoder preferences for audio formats. */ struct decoder_s_preference { struct decoder_s_preference *next; /* chain pointer */ #ifdef DEBUG const char *source; /* entry in PreferredDecoders */ #endif int decoders; /* number of decoders */ int decoder_list[PLUGINS_NUM]; /* decoder indices */ char *subtype; /* MIME subtype or NULL */ char type[]; /* MIME type or filename extn */ }; typedef struct decoder_s_preference decoder_t_preference; static decoder_t_preference *preferences = NULL; static int default_decoder_list[PLUGINS_NUM]; static char *clean_mime_subtype (char *subtype) { char *ptr; assert (subtype && subtype[0]); if (!strncasecmp (subtype, "x-", 2)) subtype += 2; ptr = strchr (subtype, ';'); if (ptr) *ptr = 0x00; return subtype; } /* Find a preference entry matching the given filename extension and/or * MIME media type, or NULL. */ static decoder_t_preference *lookup_preference (const char *extn, const char *file, char **mime) { char *type, *subtype; decoder_t_preference *result; assert ((extn && extn[0]) || (file && file[0]) || (mime && *mime && *mime[0])); type = NULL; subtype = NULL; for (result = preferences; result; result = result->next) { if (!result->subtype) { if (extn && !strcasecmp (result->type, extn)) break; } else { if (!type) { if (mime && *mime == NULL && file && file[0]) { if (options_get_bool ("UseMimeMagic")) *mime = file_mime_type (file); } if (mime && *mime && strchr (*mime, '/')) type = xstrdup (*mime); if (type) { subtype = strchr (type, '/'); *subtype++ = 0x00; subtype = clean_mime_subtype (subtype); } } if (type) { if (!strcasecmp (result->type, type) && !strcasecmp (result->subtype, subtype)) break; } } } free (type); return result; } /* Return the index of the first decoder able to handle files with the * given filename extension, or -1 if none can. */ static int find_extn_decoder (int *decoder_list, int count, const char *extn) { int ix; assert (decoder_list); assert (RANGE(0, count, plugins_num)); assert (extn && extn[0]); for (ix = 0; ix < count; ix += 1) { if (plugins[decoder_list[ix]].decoder->our_format_ext && plugins[decoder_list[ix]].decoder->our_format_ext (extn)) return decoder_list[ix]; } return -1; } /* Return the index of the first decoder able to handle audio with the * given MIME media type, or -1 if none can. */ static int find_mime_decoder (int *decoder_list, int count, const char *mime) { int ix; assert (decoder_list); assert (RANGE(0, count, plugins_num)); assert (mime && mime[0]); for (ix = 0; ix < count; ix += 1) { if (plugins[decoder_list[ix]].decoder->our_format_mime && plugins[decoder_list[ix]].decoder->our_format_mime (mime)) return decoder_list[ix]; } return -1; } /* Return the index of the first decoder able to handle audio with the * given filename extension and/or MIME media type, or -1 if none can. */ static int find_decoder (const char *extn, const char *file, char **mime) { int result; decoder_t_preference *pref; assert ((extn && extn[0]) || (file && file[0]) || (mime && *mime)); pref = lookup_preference (extn, file, mime); if (pref) { if (pref->subtype) return find_mime_decoder (pref->decoder_list, pref->decoders, *mime); else return find_extn_decoder (pref->decoder_list, pref->decoders, extn); } result = -1; if (mime && *mime) result = find_mime_decoder (default_decoder_list, plugins_num, *mime); if (result == -1 && extn && *extn) result = find_extn_decoder (default_decoder_list, plugins_num, extn); return result; } /* Find the index in plugins table for the given file. * Return -1 if not found. */ static int find_type (const char *file) { int result = -1; char *extn, *mime; extn = ext_pos (file); mime = NULL; result = find_decoder (extn, file, &mime); free (mime); return result; } int is_sound_file (const char *name) { return find_type(name) != -1 ? 1 : 0; } /* Return short type name for the given file or NULL if not found. * Not thread safe! */ char *file_type_name (const char *file) { int i; static char buf[4]; if (file_type (file) == F_URL) { strcpy (buf, "NET"); return buf; } i = find_type (file); if (i == -1) return NULL; memset (buf, 0, sizeof (buf)); if (plugins[i].decoder->get_name) plugins[i].decoder->get_name (file, buf); /* Attempt a default name if we have nothing else. */ if (!buf[0]) { char *ext; ext = ext_pos (file); if (ext) { size_t len; len = strlen (ext); for (size_t ix = 0; ix < len; ix += 1) { if (ix > 1) { buf[2] = toupper (ext[len - 1]); break; } buf[ix] = toupper (ext[ix]); } } } if (!buf[0]) return NULL; return buf; } struct decoder *get_decoder (const char *file) { int i; i = find_type (file); if (i != -1) return plugins[i].decoder; return NULL; } /* Given a decoder pointer, return its name. */ const char *get_decoder_name (const struct decoder *decoder) { int ix; const char *result = NULL; assert (decoder); for (ix = 0; ix < plugins_num; ix += 1) { if (plugins[ix].decoder == decoder) { result = plugins[ix].name; break; } } assert (result); return result; } /* Use the stream's MIME type to return a decoder for it, or NULL if no * applicable decoder was found. */ static struct decoder *get_decoder_by_mime_type (struct io_stream *stream) { int i; char *mime; struct decoder *result; result = NULL; mime = xstrdup (io_get_mime_type (stream)); if (mime) { i = find_decoder (NULL, NULL, &mime); if (i != -1) { logit ("Found decoder for MIME type %s: %s", mime, plugins[i].name); result = plugins[i].decoder; } free (mime); } else logit ("No MIME type."); return result; } /* Return the decoder for this stream. */ struct decoder *get_decoder_by_content (struct io_stream *stream) { char buf[8096]; ssize_t res; int i; struct decoder *decoder_by_mime_type; assert (stream != NULL); /* Peek at the start of the stream to check if sufficient data is * available. If not, there is no sense in trying the decoders as * each of them would issue an error. The data is also needed to * get the MIME type. */ logit ("Testing the stream..."); res = io_peek (stream, buf, sizeof (buf)); if (res < 0) { error ("Stream error: %s", io_strerror (stream)); return NULL; } if (res < 512) { logit ("Stream too short"); return NULL; } decoder_by_mime_type = get_decoder_by_mime_type (stream); if (decoder_by_mime_type) return decoder_by_mime_type; for (i = 0; i < plugins_num; i++) { if (plugins[i].decoder->can_decode && plugins[i].decoder->can_decode (stream)) { logit ("Found decoder for stream: %s", plugins[i].name); return plugins[i].decoder; } } error ("Format not supported"); return NULL; } /* Extract decoder name from file name. */ static char *extract_decoder_name (const char *filename) { int len; const char *ptr; char *result; if (!strncmp (filename, "lib", 3)) filename += 3; len = strlen (filename); ptr = strpbrk (filename, "_.-"); if (ptr) len = ptr - filename; result = xmalloc (len + 1); strncpy (result, filename, len); result[len] = 0x00; return result; } /* Return the index for a decoder of the given name, or plugins_num if * not found. */ static int lookup_decoder_by_name (const char *name) { int result; assert (name && name[0]); result = 0; while (result < plugins_num) { if (!strcasecmp (plugins[result].name, name)) break; result += 1; } return result; } /* Return a string of concatenated driver names. */ static char *list_decoder_names (int *decoder_list, int count) { int ix; char *result; lists_t_strs *names; if (count == 0) return xstrdup (""); names = lists_strs_new (count); for (ix = 0; ix < count; ix += 1) lists_strs_append (names, plugins[decoder_list[ix]].name); if (have_tremor) { ix = lists_strs_find (names, "vorbis"); if (ix < lists_strs_size (names)) lists_strs_replace (names, ix, "vorbis(tremor)"); } ix = lists_strs_find (names, "ffmpeg"); if (ix < lists_strs_size (names)) { #if defined(HAVE_FFMPEG) lists_strs_replace (names, ix, "ffmpeg"); #elif defined(HAVE_LIBAV) lists_strs_replace (names, ix, "ffmpeg(libav)"); #else lists_strs_replace (names, ix, "ffmpeg/libav"); #endif } result = lists_strs_fmt (names, " %s"); lists_strs_free (names); return result; } /* Check if this handle is already present in the plugins table. * Returns 1 if so. */ static int present_handle (const lt_dlhandle h) { int i; for (i = 0; i < plugins_num; i++) { if (plugins[i].handle == h) return 1; } return 0; } static int lt_load_plugin (const char *file, lt_ptr debug_info_ptr) { int debug_info; const char *name; union { void *data; plugin_init_func *func; } init; debug_info = *(int *)debug_info_ptr; name = strrchr (file, '/'); name = name ? (name + 1) : file; if (debug_info) printf ("Loading plugin %s...\n", name); if (plugins_num == PLUGINS_NUM) { fprintf (stderr, "Can't load plugin, because maximum number " "of plugins reached!\n"); return 0; } plugins[plugins_num].handle = lt_dlopenext (file); if (!plugins[plugins_num].handle) { fprintf (stderr, "Can't load plugin %s: %s\n", name, lt_dlerror ()); return 0; } if (present_handle (plugins[plugins_num].handle)) { if (debug_info) printf ("Already loaded\n"); if (lt_dlclose (plugins[plugins_num].handle)) fprintf (stderr, "Error unloading plugin: %s\n", lt_dlerror ()); return 0; } init.data = lt_dlsym (plugins[plugins_num].handle, "plugin_init"); if (!init.data) { fprintf (stderr, "No init function in the plugin!\n"); if (lt_dlclose (plugins[plugins_num].handle)) fprintf (stderr, "Error unloading plugin: %s\n", lt_dlerror ()); return 0; } /* If this call to init.func() fails with memory access or illegal * instruction errors then read the commit log message for r2831. */ plugins[plugins_num].decoder = init.func (); if (!plugins[plugins_num].decoder) { fprintf (stderr, "NULL decoder!\n"); if (lt_dlclose (plugins[plugins_num].handle)) fprintf (stderr, "Error unloading plugin: %s\n", lt_dlerror ()); return 0; } if (plugins[plugins_num].decoder->api_version != DECODER_API_VERSION) { fprintf (stderr, "Plugin uses different API version\n"); if (lt_dlclose (plugins[plugins_num].handle)) fprintf (stderr, "Error unloading plugin: %s\n", lt_dlerror ()); return 0; } plugins[plugins_num].name = extract_decoder_name (name); /* Is the Vorbis decoder using Tremor? */ if (!strcmp (plugins[plugins_num].name, "vorbis")) { void *vorbis_has_tremor; vorbis_has_tremor = lt_dlsym (plugins[plugins_num].handle, "vorbis_has_tremor"); have_tremor = vorbis_has_tremor != NULL; } debug ("Loaded %s decoder", plugins[plugins_num].name); if (plugins[plugins_num].decoder->init) plugins[plugins_num].decoder->init (); plugins_num += 1; if (debug_info) printf ("OK\n"); return 0; } /* Create a new preferences entry and initialise it. */ static decoder_t_preference *make_preference (const char *prefix) { decoder_t_preference *result; assert (prefix && prefix[0]); result = (decoder_t_preference *)xmalloc ( offsetof (decoder_t_preference, type) + strlen (prefix) + 1 ); result->next = NULL; result->decoders = 0; strcpy (result->type, prefix); result->subtype = strchr (result->type, '/'); if (result->subtype) { *result->subtype++ = 0x00; result->subtype = clean_mime_subtype (result->subtype); } return result; } /* Is the given decoder (by index) already in the decoder list for 'pref'? */ static bool is_listed_decoder (decoder_t_preference *pref, int d) { int ix; bool result; assert (pref); assert (d >= 0); result = false; for (ix = 0; ix < pref->decoders; ix += 1) { if (d == pref->decoder_list[ix]) { result = true; break; } } return result; } /* Add the named decoder (if valid) to a preferences decoder list. */ static void load_each_decoder (decoder_t_preference *pref, const char *name) { int d; assert (pref); assert (name && name[0]); d = lookup_decoder_by_name (name); /* Drop unknown decoders. */ if (d == plugins_num) return; /* Drop duplicate decoders. */ if (is_listed_decoder (pref, d)) return; pref->decoder_list[pref->decoders++] = d; return; } /* Build a preference's decoder list. */ static void load_decoders (decoder_t_preference *pref, lists_t_strs *tokens) { int ix, dx, asterisk_at; int decoder[PLUGINS_NUM]; const char *name; assert (pref); assert (tokens); asterisk_at = -1; /* Add the index of each known decoder to the decoders list. * Note the position following the first asterisk. */ for (ix = 1; ix < lists_strs_size (tokens); ix += 1) { name = lists_strs_at (tokens, ix); if (strcmp (name, "*")) load_each_decoder (pref, name); else if (asterisk_at == -1) asterisk_at = pref->decoders; } if (asterisk_at == -1) return; dx = 0; /* Find decoders not already listed. */ for (ix = 0; ix < plugins_num; ix += 1) { if (!is_listed_decoder (pref, ix)) decoder[dx++] = ix; } /* Splice asterisk decoders into the decoder list. */ for (ix = 0; ix < dx; ix += 1) { pref->decoder_list[pref->decoders++] = pref->decoder_list[asterisk_at + ix]; pref->decoder_list[asterisk_at + ix] = decoder[ix]; } assert (RANGE(0, pref->decoders, plugins_num)); } /* Add a new preference for an audio format. */ static void load_each_preference (const char *preference) { const char *prefix; lists_t_strs *tokens; decoder_t_preference *pref; assert (preference && preference[0]); tokens = lists_strs_new (4); lists_strs_split (tokens, preference, "(,)"); prefix = lists_strs_at (tokens, 0); pref = make_preference (prefix); #ifdef DEBUG pref->source = preference; #endif load_decoders (pref, tokens); pref->next = preferences; preferences = pref; lists_strs_free (tokens); } /* Load all preferences given by the user in PreferredDecoders. */ static void load_preferences () { int ix; const char *preference; lists_t_strs *list; list = options_get_list ("PreferredDecoders"); for (ix = 0; ix < lists_strs_size (list); ix += 1) { preference = lists_strs_at (list, ix); load_each_preference (preference); } #ifdef DEBUG { char *names; decoder_t_preference *pref; for (pref = preferences; pref; pref = pref->next) { names = list_decoder_names (pref->decoder_list, pref->decoders); debug ("%s:%s", pref->source, names); free (names); } } #endif } static void load_plugins (int debug_info) { int ix; char *names; if (debug_info) printf ("Loading plugins from %s...\n", PLUGIN_DIR); if (lt_dlinit ()) fatal ("lt_dlinit() failed: %s", lt_dlerror ()); if (lt_dlforeachfile (PLUGIN_DIR, <_load_plugin, &debug_info)) fatal ("Can't load plugins: %s", lt_dlerror ()); if (plugins_num == 0) fatal ("No decoder plugins have been loaded!"); for (ix = 0; ix < plugins_num; ix += 1) default_decoder_list[ix] = ix; names = list_decoder_names (default_decoder_list, plugins_num); logit ("Loaded %d decoders:%s", plugins_num, names); free (names); } void decoder_init (int debug_info) { load_plugins (debug_info); load_preferences (); } static void cleanup_decoders () { int ix; for (ix = 0; ix < plugins_num; ix++) { if (plugins[ix].decoder->destroy) plugins[ix].decoder->destroy (); free (plugins[ix].name); if (plugins[ix].handle) lt_dlclose (plugins[ix].handle); } if (lt_dlexit ()) logit ("lt_exit() failed: %s", lt_dlerror ()); } static void cleanup_preferences () { decoder_t_preference *pref, *next; pref = preferences; for (pref = preferences; pref; pref = next) { next = pref->next; free (pref); } preferences = NULL; } void decoder_cleanup () { cleanup_decoders (); cleanup_preferences (); } /* Fill the error structure with an error of a given type and message. * strerror(add_errno) is appended at the end of the message if add_errno != 0. * The old error message is free()ed. * This is thread safe; use this instead of constructs using strerror(). */ void decoder_error (struct decoder_error *error, const enum decoder_error_type type, const int add_errno, const char *format, ...) { char *err_str; va_list va; if (error->err) free (error->err); error->type = type; va_start (va, format); err_str = format_msg_va (format, va); va_end (va); if (add_errno) { char *err_buf; err_buf = xstrerror (add_errno); error->err = format_msg ("%s%s", err_str, err_buf); free (err_buf); } else error->err = format_msg ("%s", err_str); free (err_str); } /* Initialize the decoder_error structure. */ void decoder_error_init (struct decoder_error *error) { error->type = ERROR_OK; error->err = NULL; } /* Set the decoder_error structure to contain "success" information. */ void decoder_error_clear (struct decoder_error *error) { error->type = ERROR_OK; if (error->err) { free (error->err); error->err = NULL; } } void decoder_error_copy (struct decoder_error *dst, const struct decoder_error *src) { dst->type = src->type; dst->err = xstrdup (src->err); } /* Return the error text from the decoder_error variable. */ const char *decoder_error_text (const struct decoder_error *error) { return error->err; } moc-2.6.0~svn-r3005/decoder.h0000664000076400000500000002440413012456764014721 0ustar riesebiesrc#ifndef DECODER_H #define DECODER_H #include "audio.h" #include "playlist.h" #include "io.h" #ifdef __cplusplus extern "C" { #endif /** Version of the decoder API. * * On every change in the decoder API this number will be changed, so * MOC will not load plugins compiled with older/newer decoder.h. */ #define DECODER_API_VERSION 7 /** Type of the decoder error. */ enum decoder_error_type { ERROR_OK, /*!< There was no error. */ ERROR_STREAM, /*!< Recoverable error in the stream. */ ERROR_FATAL /*!< Fatal error in the stream - further decoding can't be performed. */ }; /** Decoder error. * * Describes decoder error. Fields don't need to be accessed directly, * there are functions to modify/access decoder_error object. */ struct decoder_error { enum decoder_error_type type; /*!< Type of the error. */ char *err; /*!< malloc()ed error string or NULL. */ }; /** @struct decoder * Functions provided by the decoder plugin. * * Describes decoder - contains pointers to decoder's functions. If some * field is optional, it may have NULL value. */ struct decoder { /** API version used by the plugin. * * Set it to DECODER_API_VERSION to recognize if MOC and the plugin can * communicate. This is the first field in the structure, so even after * changing other fields it will hopefully be always read properly. */ int api_version; /** Initialize the plugin. * * This function is called once at MOC startup (once for the client and * once for the server). Optional. */ void (*init) (); /** Cleanup the plugin. * * This function is called once at exit (once for the client and * once for the server). Optional. */ void (*destroy) (); /** Open the resource. * * Open the given resource (file). * * \param uri URL to the resource that can be used as the file parameter * and return pointer to io_open(). * * \return Private decoder data. This pointer will be passed to every * other function that operates on the stream. */ void *(*open)(const char *uri); /** Open the resource for an already opened stream. * * Handle the stream that was already opened, but no data were read. * You must operate on the stream using io_*() functions. This is used * for internet streams, so seeking is not possible. This function is * optional. * * \param stream Opened stream from which the decoder must read. * * \return Private decoder data. This pointer will be passed to every * other function that operates on the stream. */ void *(*open_stream)(struct io_stream *stream); /** Check if the decoder is able to decode from this stream. * * Used to check if the decoder is able to read from an already opened * stream. This is used to find the proper decoder for an internet * stream when searching by the MIME type failed. The decoder must not * read from this stream (io_read()), but can peek data (io_peek()). * The decoder is expected to peek a few bytes to recognize its format. * Optional. * * \param stream Opened stream. * * \return 1 if the decoder is able to decode data from this stream. */ int (*can_decode)(struct io_stream *stream); /** Close the resource and cleanup. * * Free all decoder's private data and allocated resources. * * \param data Decoder's private data. */ void (*close)(void *data); /** Decode a piece of input. * * Decode a piece of input and write it to the buffer. The buffer size * is at least 32KB, but don't make any assumptions that it is always * true. It is preferred that as few bytes as possible be decoded * without loss of performance to minimise delays. * * \param data Decoder's private data. * \param buf Buffer to put data in. * \param buf_len Size of the buffer in bytes. * \param sound_params Parameters of the decoded sound. This must be * always filled. * * \return Number of bytes written or 0 on EOF. */ int (*decode)(void *data, char *buf, int buf_len, struct sound_params *sound_params); /** Seek in the stream. * * Seek to the given position. * * \param data Decoder's private data. * \param sec Where to seek in seconds (never less than zero). * * \return The position that we actually seek to or -1 on error. * -1 is not a fatal error and further decoding will be performed. */ int (*seek)(void *data, int sec); /** Get tags for a file. * * Get requested file's tags. If some tags are not available, the * decoder can just not fill the field. The function can even not * fill any field. * * \param file File for which to get tags. * \param tags Pointer to the tags structure where we must put * the tags. All strings must be malloc()ed. * \param tags_sel OR'ed list of requested tags (values of * enum tags_select). */ void (*info)(const char *file, struct file_tags *tags, const int tags_sel); /** Get the current bitrate. * * Get the bitrate of the last decoded piece of sound. * * \param data Decoder's private data. * * \return Current bitrate in kbps or -1 if not available. */ int (*get_bitrate)(void *data); /** Get duration of the stream. * * Get duration of the stream. It is used as a faster alternative * for getting duration than using info() if the file is opened. * * \param data Decoder's private data. * * \return Duration in seconds or -1 on error. -1 is not a fatal * error, further decoding will be performed. */ int (*get_duration)(void *data); /** Get error for the last decode() invocation. * * Get the error for the last decode() invocation. If there was no * error the type of the error must be ERROR_OK. Don't access the * error object's fields directly, there are proper functions for * that. * * \param data Decoder's private data. * \param error Pointer to the decoder_error object to fill. */ void (*get_error)(void *data, struct decoder_error *error); /** Check if the file extension is for a file that this decoder * supports. * * \param ext Extension (chars after the last dot in the file name). * * \return Value other than 0 if the extension if a file with this * extension is supported. */ int (*our_format_ext)(const char *ext); /** Check if a stream with the given MIME type is supported by this * decoder. Optional. * * \param mime_type MIME type. * * \return Value other than 0 if a stream with this MIME type is * supported. */ int (*our_format_mime)(const char *mime_type); /** Get a 3-chars format name for a file. * * Get an abbreviated format name (up to 3 chars) for a file. * This function is optional. * * \param file File for which we want the format name. * \param buf Buffer where the nul-terminated format name may be put. */ void (*get_name)(const char *file, char buf[4]); /** Get current tags for the stream. * * Fill the tags structure with the current tags for the stream. This * is intended for internet streams and used when the source of the * stream doesn't provide tags while broadcasting. This function is * optional. * * \param data Decoder's private data. * * \return 1 if the tags were changed from the last call of this * function or 0 if not. */ int (*current_tags)(void *data, struct file_tags *tags); /** Get the IO stream used by the decoder. * * Get the pointer to the io_stream object used by the decoder. This * is used for fast interrupting especially when the stream reads * from a network. This function is optional. * * \param data Decoder's private data. * * \return Pointer to the used IO stream. */ struct io_stream *(*get_stream)(void *data); /** Get the average bitrate. * * Get the bitrate of the whole file. * * \param data Decoder's private data. * * \return Average bitrate in kbps or -1 if not available. */ int (*get_avg_bitrate)(void *data); }; /** Initialize decoder plugin. * * Each decoder plugin must export a function name plugin_init of this * type. The function must return a pointer to the struct decoder variable * filled with pointers to decoder's functions. */ typedef struct decoder *plugin_init_func (); int is_sound_file (const char *name); struct decoder *get_decoder (const char *file); struct decoder *get_decoder_by_content (struct io_stream *stream); const char *get_decoder_name (const struct decoder *decoder); void decoder_init (int debug_info); void decoder_cleanup (); char *file_type_name (const char *file); /** @defgroup decoder_error_funcs Decoder error functions * * These functions can be used to modify variables of the decoder_error * structure. */ /*@{*/ /** Fill decoder_error structure with an error. * * Fills decoder error variable with an error. It can be used like printf(). * * \param error Pointer to the decoder_error object to fill. * \param type Type of the error. * \param add_errno If this value is non-zero, a space and a string * describing system error for errno equal to the value of add_errno * is appended to the error message. * \param format Format, like in the printf() function. */ void decoder_error (struct decoder_error *error, const enum decoder_error_type type, const int add_errno, const char *format, ...) ATTR_PRINTF(4, 5); /** Clear decoder_error structure. * * Clear decoder_error structure. Set the system type to ERROR_OK and * the error message to NULL. Frees all memory used by the error's fields. * * \param error Pointer to the decoder_error object to be cleared. */ void decoder_error_clear (struct decoder_error *error); /** Copy decoder_error variable. * * Copies the decoder_error variable to another decoder_error variable. * * \param dst Destination. * \param src Source. */ void decoder_error_copy (struct decoder_error *dst, const struct decoder_error *src); /** Return the error text from the decoder_error variable. * * Returns the error text from the decoder_error variable. NULL may be * returned if decoder_error() has not been called. * * \param error Pointer to the source decoder_error object. * * \return The address of the error text or NULL. */ const char *decoder_error_text (const struct decoder_error *error); /** Initialize decoder_error variable. * * Initialize decoder_error variable and set the error to ERROR_OK with no * message. * * \param error Pointer to the decoder_error object to be initialised. */ void decoder_error_init (struct decoder_error *error); /*@}*/ #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/decoder_plugins/0002775000076400000500000000000013710016723016277 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/Makefile.am0000664000076400000500000000104611537065437020345 0ustar riesebiesrcSUBDIRS = if BUILD_mp3 SUBDIRS += mp3 endif if BUILD_aac SUBDIRS += aac endif if BUILD_musepack SUBDIRS += musepack endif if BUILD_vorbis SUBDIRS += vorbis endif if BUILD_flac SUBDIRS += flac endif if BUILD_wavpack SUBDIRS += wavpack endif if BUILD_sndfile SUBDIRS += sndfile endif if BUILD_modplug SUBDIRS += modplug endif if BUILD_timidity SUBDIRS += timidity endif if BUILD_sidplay2 SUBDIRS += sidplay2 endif if BUILD_ffmpeg SUBDIRS += ffmpeg endif if BUILD_speex SUBDIRS += speex endif moc-2.6.0~svn-r3005/decoder_plugins/aac/0002775000076400000500000000000013710016723017023 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/aac/Makefile.am0000664000076400000500000000042010655151360021053 0ustar riesebiesrclib_LTLIBRARIES = libaac_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libaac_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libaac_decoder_la_LIBADD = -lid3tag -lz $(FAAD2_LIBS) libaac_decoder_la_CFLAGS = $(FAAD2_CFLAGS) -I$(top_srcdir) libaac_decoder_la_SOURCES = aac.c moc-2.6.0~svn-r3005/decoder_plugins/aac/aac.c0000664000076400000500000003374513537054443017735 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005, 2006 Damian Pietras * * 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 code is based on CMUS aac plugin Copyright 2006 dnk * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "decoder.h" #include "io.h" #include "log.h" #include "files.h" /* FAAD_MIN_STREAMSIZE == 768, 6 == # of channels */ #define BUFFER_SIZE (FAAD_MIN_STREAMSIZE * 6 * 4) struct aac_data { struct io_stream *stream; char rbuf[BUFFER_SIZE]; int rbuf_len; int rbuf_pos; int channels; int sample_rate; char *overflow_buf; int overflow_buf_len; NeAACDecHandle decoder; /* typedef void * */ int ok; /* was this stream successfully opened? */ struct decoder_error error; int bitrate; int avg_bitrate; int duration; }; static int buffer_length (const struct aac_data *data) { return data->rbuf_len - data->rbuf_pos; } static void *buffer_data (struct aac_data *data) { return data->rbuf + data->rbuf_pos; } static int buffer_fill (struct aac_data *data) { ssize_t n; if (data->rbuf_pos > 0) { data->rbuf_len = buffer_length (data); memmove (data->rbuf, data->rbuf + data->rbuf_pos, data->rbuf_len); data->rbuf_pos = 0; } if (data->rbuf_len == BUFFER_SIZE) return 1; n = io_read (data->stream, data->rbuf + data->rbuf_len, BUFFER_SIZE - data->rbuf_len); if (n == -1) return -1; if (n == 0) return 0; data->rbuf_len += n; return 1; } static inline void buffer_flush (struct aac_data *data) { data->rbuf_len = 0; data->rbuf_pos = 0; } static inline void buffer_consume (struct aac_data *data, int n) { assert (n <= buffer_length(data)); data->rbuf_pos += n; } static int buffer_fill_min (struct aac_data *data, int len) { int rc; assert (len < BUFFER_SIZE); while (buffer_length(data) < len) { rc = buffer_fill (data); if (rc <= 0) return rc; } return 1; } /* 'data' must point to at least 6 bytes of data */ static int parse_frame (const unsigned char data[6]) { int len; /* http://wiki.multimedia.cx/index.php?title=ADTS */ /* first 12 bits must be set */ if (data[0] != 0xFF) return 0; if ((data[1] & 0xF0) != 0xF0) return 0; /* layer is always '00' */ if ((data[1] & 0x06) != 0x00) return 0; /* frame length is stored in 13 bits */ len = data[3] << 11; /* ..1100000000000 */ len |= data[4] << 3; /* ..xx11111111xxx */ len |= data[5] >> 5; /* ..xxxxxxxxxx111 */ len &= 0x1FFF; /* 13 bits */ return len; } /* scans forward to the next aac frame and makes sure * the entire frame is in the buffer. */ static int buffer_fill_frame(struct aac_data *data) { unsigned char *datap; int rc, n, len; int max = 32768; while (1) { /* need at least 6 bytes of data */ rc = buffer_fill_min(data, 6); if (rc <= 0) break; len = buffer_length(data); datap = buffer_data(data); /* scan for a frame */ for (n = 0; n < len - 5; n++) { /* give up after 32KB */ if (max-- == 0) { logit ("no frame found!"); /* FIXME: set errno? */ return -1; } /* see if there's a frame at this location */ rc = parse_frame(datap + n); if (rc == 0) continue; /* found a frame, consume all data up to the frame */ buffer_consume (data, n); /* rc == frame length */ rc = buffer_fill_min (data, rc); if (rc <= 0) goto end; return 1; } /* consume what we used */ buffer_consume (data, n); } end: return rc; } /* This should be called with a unique decoder instance as the seeking * it does triggers an FAAD bug which results in distorted audio due to * retained state being corrupted. (One suspects NeAACDecPostSeekReset() * should resolve the problem but experimentation suggests not and no * documentation exists describing its use.) */ static int aac_count_time (struct aac_data *data) { NeAACDecFrameInfo frame_info; int samples = 0, bytes = 0, frames = 0; off_t file_size; int16_t *sample_buf; file_size = io_file_size (data->stream); if (file_size == -1) return -1; if (io_seek(data->stream, file_size / 2, SEEK_SET) == -1) return -1; buffer_flush (data); /* Guess track length by decoding the middle 50 frames which have * more than 25% of non-zero samples having absolute values greater * than 16. */ while (frames < 50) { if (buffer_fill_frame (data) <= 0) break; sample_buf = NeAACDecDecode (data->decoder, &frame_info, buffer_data (data), buffer_length (data)); if (frame_info.error == 0 && frame_info.samples > 0) { unsigned int ix, zeroes = 0; for (ix = 0; ix < frame_info.samples; ix += 1) { if (sample_buf[ix] != 0 && RANGE(-16, sample_buf[ix], 16)) zeroes += 1; } if (zeroes * 4 < frame_info.samples) { samples += frame_info.samples; bytes += frame_info.bytesconsumed; frames += 1; } } if (frame_info.bytesconsumed == 0) break; buffer_consume (data, frame_info.bytesconsumed); } if (frames == 0) return -1; samples /= frames; samples /= data->channels; bytes /= frames; return ((file_size / bytes) * samples) / data->sample_rate; } static struct aac_data *aac_open_internal (struct io_stream *stream, const char *fname, bool timing_only) { struct aac_data *data; NeAACDecConfigurationPtr neaac_cfg; unsigned char channels; unsigned long sample_rate; int n; /* init private struct */ data = xcalloc (1, sizeof *data); data->decoder = NeAACDecOpen(); /* set decoder config */ neaac_cfg = NeAACDecGetCurrentConfiguration(data->decoder); neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */ neaac_cfg->downMatrix = !timing_only; /* 5.1 -> stereo */ neaac_cfg->dontUpSampleImplicitSBR = 0; /* upsample, please! */ NeAACDecSetConfiguration(data->decoder, neaac_cfg); if (stream) data->stream = stream; else { data->stream = io_open (fname, 1); if (!io_ok(data->stream)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open AAC file: %s", io_strerror(data->stream)); return data; } } /* find a frame */ if (buffer_fill_frame(data) <= 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Not a valid (or unsupported) AAC file"); return data; } /* in case of a bug, make sure there is at least some data * in the buffer for NeAACDecInit() to work with. */ if (buffer_fill_min(data, 256) <= 0) { decoder_error (&data->error, ERROR_FATAL, 0, "AAC file/stream too short"); return data; } /* init decoder, returns the length of the header (if any) */ channels = (unsigned char)data->channels; sample_rate = data->sample_rate; n = NeAACDecInit (data->decoder, buffer_data(data), buffer_length(data), &sample_rate, &channels); data->channels = channels; data->sample_rate = (int)sample_rate; if (n < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "libfaad can't open this stream"); return data; } if (!timing_only) { if (data->channels == 6) { logit ("sample rate %dHz, channels %d (downmixed to stereo)", data->sample_rate, data->channels); data->channels = 2; } else logit ("sample rate %dHz, channels %d", data->sample_rate, data->channels); } if (!data->sample_rate || !data->channels) { decoder_error (&data->error, ERROR_FATAL, 0, "Invalid AAC sound parameters"); return data; } /* skip the header */ logit ("skipping header (%d bytes)", n); buffer_consume (data, n); /*NeAACDecInitDRM(data->decoder, data->sample_rate, data->channels);*/ data->ok = 1; return data; } static void aac_close (void *prv_data) { struct aac_data *data = (struct aac_data *)prv_data; NeAACDecClose (data->decoder); io_close (data->stream); decoder_error_clear (&data->error); free (data); } static void *aac_open (const char *file) { struct aac_data *data; data = aac_open_internal (NULL, file, true); if (data->ok) { int duration = -1; int avg_bitrate = -1; off_t file_size; duration = aac_count_time (data); file_size = io_file_size (data->stream); if (duration > 0 && file_size != -1) avg_bitrate = file_size / duration * 8; aac_close (data); data = aac_open_internal (NULL, file, false); data->duration = duration; data->avg_bitrate = avg_bitrate; } return data; } static void *aac_open_stream (struct io_stream *stream) { assert (stream != NULL); return aac_open_internal (stream, NULL, false); } static char *get_tag (struct id3_tag *tag, const char *what) { struct id3_frame *frame; union id3_field *field; const id3_ucs4_t *ucs4; char *comm = NULL; frame = id3_tag_findframe (tag, what, 0); if (frame && (field = &frame->fields[1])) { ucs4 = id3_field_getstrings (field, 0); if (ucs4) comm = (char *)id3_ucs4_utf8duplicate (ucs4); } return comm; } /* Fill info structure with data from aac comments */ static void aac_info (const char *file_name, struct file_tags *info, const int tags_sel) { if (tags_sel & TAGS_COMMENTS) { struct id3_tag *tag; struct id3_file *id3file; char *track = NULL; id3file = id3_file_open (file_name, ID3_FILE_MODE_READONLY); if (!id3file) return; tag = id3_file_tag (id3file); if (tag) { info->artist = get_tag (tag, ID3_FRAME_ARTIST); info->title = get_tag (tag, ID3_FRAME_TITLE); info->album = get_tag (tag, ID3_FRAME_ALBUM); track = get_tag (tag, ID3_FRAME_TRACK); if (track) { char *end; info->track = strtol (track, &end, 10); if (end == track) info->track = -1; free (track); } } id3_file_close (id3file); } if (tags_sel & TAGS_TIME) { struct aac_data *data; data = aac_open_internal (NULL, file_name, true); if (data->ok) info->time = aac_count_time (data); else logit ("%s", decoder_error_text (&data->error)); aac_close (data); } } static int aac_seek (void *unused ATTR_UNUSED, int sec ASSERT_ONLY) { assert (sec >= 0); /* AAC will probably never be able to seek. There is no way of * relating the time in the audio to the position in the file * short of pre-processing the file at open and building a seek * table. Even then, seeking in the file causes audio glitches * (see aac_count_time()). */ return -1; } /* returns -1 on fatal errors * returns -2 on non-fatal errors * 0 on eof * number of bytes put in 'buffer' on success */ static int decode_one_frame (struct aac_data *data, void *buffer, int count) { unsigned char *aac_data; unsigned int aac_data_size; NeAACDecFrameInfo frame_info; char *sample_buf; int bytes, rc; rc = buffer_fill_frame (data); if (rc <= 0) return rc; aac_data = buffer_data (data); aac_data_size = buffer_length (data); /* aac data -> raw pcm */ sample_buf = NeAACDecDecode (data->decoder, &frame_info, aac_data, aac_data_size); buffer_consume (data, frame_info.bytesconsumed); if (!sample_buf || frame_info.bytesconsumed <= 0) { decoder_error (&data->error, ERROR_FATAL, 0, "%s", NeAACDecGetErrorMessage (frame_info.error)); return -1; } if (frame_info.error != 0) { decoder_error (&data->error, ERROR_STREAM, 0, "%s", NeAACDecGetErrorMessage (frame_info.error)); return -2; } if (frame_info.samples <= 0) return -2; if (frame_info.channels != (unsigned char)data->channels || frame_info.samplerate != (unsigned long)data->sample_rate) { decoder_error (&data->error, ERROR_STREAM, 0, "%s", "Invalid channel or sample_rate count"); return -2; } /* 16-bit samples */ bytes = frame_info.samples * 2; if (bytes > count) { /* decoded too much, keep overflow */ data->overflow_buf = sample_buf + count; data->overflow_buf_len = bytes - count; memcpy (buffer, sample_buf, count); return count; } memcpy (buffer, sample_buf, bytes); data->bitrate = frame_info.bytesconsumed * 8 / (bytes / 2.0 / data->channels / data->sample_rate) / 1000; return bytes; } static int aac_decode (void *prv_data, char *buf, int buf_len, struct sound_params *sound_params) { struct aac_data *data = (struct aac_data *)prv_data; int rc; decoder_error_clear (&data->error); sound_params->channels = data->channels; sound_params->rate = data->sample_rate; sound_params->fmt = SFMT_S16 | SFMT_NE; /* use overflow from previous call (if any) */ if (data->overflow_buf_len) { int len; len = MIN(data->overflow_buf_len, buf_len); memcpy (buf, data->overflow_buf, len); data->overflow_buf += len; data->overflow_buf_len -= len; return len; } do { rc = decode_one_frame (data, buf, buf_len); } while (rc == -2); return MAX(rc, 0); } static int aac_get_bitrate (void *prv_data) { struct aac_data *data = (struct aac_data *)prv_data; return data->bitrate; } static int aac_get_avg_bitrate (void *prv_data) { struct aac_data *data = (struct aac_data *)prv_data; return data->avg_bitrate / 1000; } static int aac_get_duration (void *prv_data) { struct aac_data *data = (struct aac_data *)prv_data; return data->duration; } static void aac_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "AAC"); } static int aac_our_format_ext (const char *ext) { return !strcasecmp (ext, "aac"); } static void aac_get_error (void *prv_data, struct decoder_error *error) { struct aac_data *data = (struct aac_data *)prv_data; decoder_error_copy (error, &data->error); } static int aac_our_mime (const char *mime) { return !strcasecmp (mime, "audio/aac") || !strncasecmp (mime, "audio/aac;", 10) || !strcasecmp (mime, "audio/aacp") || !strncasecmp (mime, "audio/aacp;", 11); } static struct decoder aac_decoder = { DECODER_API_VERSION, NULL, NULL, aac_open, aac_open_stream, NULL, aac_close, aac_decode, aac_seek, aac_info, aac_get_bitrate, aac_get_duration, aac_get_error, aac_our_format_ext, aac_our_mime, aac_get_name, NULL, NULL, aac_get_avg_bitrate }; struct decoder *plugin_init () { return &aac_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/aac/aac.m40000664000076400000500000000127511611375037020020 0ustar riesebiesrcdnl libfaad2 (aac) AC_ARG_WITH(aac, AS_HELP_STRING([--without-aac], [Compile without AAC support (libfaad2)])) if test "x$with_aac" != "xno" then faad2_OK="no" AC_CHECK_LIB(faad, NeAACDecInit, [faad2_OK="yes"]) if test "x$faad2_OK" = "xyes"; then AC_CHECK_HEADER([neaacdec.h], , AC_MSG_ERROR([You need a more recent libfaad2 (libfaad2 devel package).])) fi if test "x$faad2_OK" = "xyes" -a "$HAVE_ID3TAG" = "yes" then FAAD2_LIBS="-lfaad" AC_SUBST([FAAD2_CFLAGS]) AC_SUBST([FAAD2_LIBS]) want_aac="yes" DECODER_PLUGINS="$DECODER_PLUGINS aac" fi fi AM_CONDITIONAL([BUILD_aac], [test "$want_aac"]) AC_CONFIG_FILES([decoder_plugins/aac/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/decoders.m40000664000076400000500000000172111611375037020334 0ustar riesebiesrcDECODER_PLUGIN_DIR=decoder_plugins AC_SUBST([DECODER_PLUGIN_DIR]) dnl libid3tag (with zlib) AC_CHECK_LIB(z, gzopen, [HAVE_ZLIB=yes],) AC_CHECK_LIB(id3tag, id3_file_open, [HAVE_LIBID3TAG=yes],) AC_CHECK_HEADER([id3tag.h],[HAVE_LIBID3TAG_H=yes],) if test "x$HAVE_ZLIB" = "xyes" -a "x$HAVE_LIBID3TAG" = "xyes" \ -a "x$HAVE_LIBID3TAG_H" = "xyes" then HAVE_ID3TAG=yes else HAVE_ID3TAG=no fi m4_include(decoder_plugins/aac/aac.m4) m4_include(decoder_plugins/ffmpeg/ffmpeg.m4) m4_include(decoder_plugins/flac/flac.m4) m4_include(decoder_plugins/modplug/modplug.m4) m4_include(decoder_plugins/mp3/mp3.m4) m4_include(decoder_plugins/musepack/musepack.m4) m4_include(decoder_plugins/sidplay2/sidplay2.m4) m4_include(decoder_plugins/sndfile/sndfile.m4) m4_include(decoder_plugins/speex/speex.m4) m4_include(decoder_plugins/timidity/timidity.m4) m4_include(decoder_plugins/vorbis/vorbis.m4) m4_include(decoder_plugins/wavpack/wavpack.m4) AC_CONFIG_FILES([decoder_plugins/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/ffmpeg/0002775000076400000500000000000013710016723017543 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/ffmpeg/Makefile.am0000664000076400000500000000053211765610235021603 0ustar riesebiesrclib_LTLIBRARIES = libffmpeg_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libffmpeg_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libffmpeg_decoder_la_LIBADD = $(ffmpeg_LIBS) libffmpeg_decoder_la_CPPFLAGS = $(ffmpeg_CPPFLAGS) -I$(top_srcdir) libffmpeg_decoder_la_CFLAGS = $(ffmpeg_CFLAGS) -I$(top_srcdir) libffmpeg_decoder_la_SOURCES = ffmpeg.c moc-2.6.0~svn-r3005/decoder_plugins/ffmpeg/ffmpeg.c0000664000076400000500000010353013435354513021161 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005, 2006 Damian Pietras * * 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. * * Based on FFplay Copyright (c) 2003 Fabrice Bellard * */ /* * "The main problem is that external projects who want to * support both FFmpeg and LibAV are just fucked, and this * only because LibAV doesn't care a second about their users." * * -- http://blog.pkh.me/p/13-the-ffmpeg-libav-situation.html */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #if HAVE_LIBAVUTIL_CHANNEL_LAYOUT_H # include #else # include #endif /* FFmpeg also likes common names, without that, our common.h and log.h * would not be included. */ #undef COMMON_H #undef LOG_H #define DEBUG #define STRERROR_FN ffmpeg_strerror #include "common.h" #include "audio.h" #include "decoder.h" #include "log.h" #include "files.h" #include "lists.h" #ifndef AV_CODEC_CAP_DELAY # define AV_CODEC_CAP_DELAY CODEC_CAP_DELAY # define AV_CODEC_CAP_EXPERIMENTAL CODEC_CAP_EXPERIMENTAL # define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED #endif #ifndef AV_CODEC_FLAG_TRUNCATED # define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED #endif /* Set SEEK_IN_DECODER to 1 if you'd prefer seeking to be delay until * the next time ffmpeg_decode() is called. This will provide seeking * in formats for which FFmpeg falsely reports seek errors, but could * result erroneous current time values. */ #define SEEK_IN_DECODER 0 struct ffmpeg_data { AVFormatContext *ic; AVIOContext *pb; AVStream *stream; AVCodecContext *enc; AVCodec *codec; char *remain_buf; int remain_buf_len; bool delay; /* FFmpeg may buffer samples */ bool eof; /* end of file seen */ bool eos; /* end of sound seen */ bool okay; /* was this stream successfully opened? */ char *filename; struct io_stream *iostream; struct decoder_error error; long fmt; int sample_width; int bitrate; /* in bits per second */ int avg_bitrate; /* in bits per second */ #if SEEK_IN_DECODER bool seek_req; /* seek requested */ int seek_sec; /* second to which to seek */ #endif bool seek_broken; /* FFmpeg seeking is broken */ bool timing_broken; /* FFmpeg trashes duration and bit_rate */ #if SEEK_IN_DECODER && defined(DEBUG) pthread_t thread_id; #endif }; struct extn_list { const char *extn; const char *format; }; static lists_t_strs *supported_extns = NULL; static void ffmpeg_log_repeats (char *msg LOGIT_ONLY) { #ifndef NDEBUG static int msg_count = 0; static char *prev_msg = NULL; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /* We need to gate the decoder and precaching threads. */ LOCK (mutex); if (prev_msg && (!msg || strcmp (msg, prev_msg))) { if (msg_count > 1) logit ("FFmpeg said: Last message repeated %d times", msg_count); free (prev_msg); prev_msg = NULL; msg_count = 0; } if (prev_msg && msg) { free (msg); msg = NULL; msg_count += 1; } if (!prev_msg && msg) { int count, ix; lists_t_strs *lines; lines = lists_strs_new (4); count = lists_strs_split (lines, msg, "\n"); for (ix = 0; ix < count; ix += 1) logit ("FFmpeg said: %s", lists_strs_at (lines, ix)); lists_strs_free (lines); prev_msg = msg; msg_count = 1; } UNLOCK (mutex); #endif } #ifndef NDEBUG static void ffmpeg_log_cb (void *unused ATTR_UNUSED, int level, const char *fmt, va_list vl) { int len; char *msg; assert (fmt); if (level > av_log_get_level ()) return; msg = format_msg_va (fmt, vl); #if defined(HAVE_FFMPEG) && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56,33,101) /* Drop this message because it is issued repeatedly and is pointless. */ const char skipping[] = "Skipping 0 bytes of junk"; if (!strncmp (skipping, msg, sizeof (skipping) - 1)) { free (msg); return; } #endif len = strlen (msg); for (len = strlen (msg); len > 0 && msg[len - 1] == '\n'; len -= 1) msg[len - 1] = 0x00; ffmpeg_log_repeats (msg); } #endif /* FFmpeg-provided error code to description function wrapper. */ static inline char *ffmpeg_strerror (int errnum) { char *result; ffmpeg_log_repeats (NULL); result = xmalloc (256); av_strerror (errnum, result, 256); result[255] = 0; return result; } /* Find the first audio stream and return its index, or nb_streams if * none found. */ static unsigned int find_first_audio (AVFormatContext *ic) { unsigned int result; assert (ic); for (result = 0; result < ic->nb_streams; result += 1) { enum AVMediaType codec_type; #ifdef HAVE_STRUCT_AVSTREAM_CODECPAR codec_type = ic->streams[result]->codecpar->codec_type; #else codec_type = ic->streams[result]->codec->codec_type; #endif if (codec_type == AVMEDIA_TYPE_AUDIO) break; } return result; } static void load_audio_extns (lists_t_strs *list) { int ix; /* When adding an entry to this list, tests need to be performed to * determine whether or not FFmpeg/LibAV handles durations and seeking * correctly. If not, then the appropriate additions should be made * in is_timing_broken() and is_seek_broken(). */ const struct extn_list audio_extns[] = { {"aac", "aac"}, {"ac3", "ac3"}, {"ape", "ape"}, {"au", "au"}, {"ay", "libgme"}, {"dff", "dsf"}, {"dsf", "dsf"}, {"dts", "dts"}, {"eac3", "eac3"}, {"fla", "flac"}, {"flac", "flac"}, {"gbs", "libgme"}, {"gym", "libgme"}, {"hes", "libgme"}, {"kss", "libgme"}, {"mka", "matroska"}, {"mp2", "mpeg"}, {"mp3", "mp3"}, {"mpc", "mpc"}, {"mpc8", "mpc8"}, {"m4a", "m4a"}, {"nsf", "libgme"}, {"nsfe", "libgme"}, {"ra", "rm"}, {"sap", "libgme"}, {"spc", "libgme"}, {"tta", "tta"}, {"vgm", "libgme"}, {"vgz", "libgme"}, {"vqf", "vqf"}, {"wav", "wav"}, {"w64", "w64"}, {"wma", "asf"}, {"wv", "wv"}, {NULL, NULL} }; for (ix = 0; audio_extns[ix].extn; ix += 1) { if (av_find_input_format (audio_extns[ix].format)) lists_strs_append (list, audio_extns[ix].extn); } if (av_find_input_format ("ogg")) { lists_strs_append (list, "ogg"); if (avcodec_find_decoder (AV_CODEC_ID_VORBIS)) lists_strs_append (list, "oga"); if (avcodec_find_decoder (AV_CODEC_ID_OPUS)) lists_strs_append (list, "opus"); if (avcodec_find_decoder (AV_CODEC_ID_THEORA)) lists_strs_append (list, "ogv"); } /* In theory, FFmpeg supports Speex if built with libspeex enabled. * In practice, it breaks badly. */ #if 0 if (avcodec_find_decoder (AV_CODEC_ID_SPEEX)) lists_strs_append (list, "spx"); #endif } static void load_video_extns (lists_t_strs *list) { int ix; const struct extn_list video_extns[] = { {"avi", "avi"}, {"flv", "flv"}, {"mkv", "matroska"}, {"mp4", "mp4"}, {"rec", "mpegts"}, {"vob", "mpeg"}, {"webm", "matroska"}, {NULL, NULL} }; for (ix = 0; video_extns[ix].extn; ix += 1) { if (av_find_input_format (video_extns[ix].format)) lists_strs_append (list, video_extns[ix].extn); } } /* Handle FFmpeg's locking requirements. */ #if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100) static int locking_cb (void **mutex, enum AVLockOp op) { int result; switch (op) { case AV_LOCK_CREATE: *mutex = xmalloc (sizeof (pthread_mutex_t)); result = pthread_mutex_init (*mutex, NULL); break; case AV_LOCK_OBTAIN: result = pthread_mutex_lock (*mutex); break; case AV_LOCK_RELEASE: result = pthread_mutex_unlock (*mutex); break; case AV_LOCK_DESTROY: result = pthread_mutex_destroy (*mutex); free (*mutex); *mutex = NULL; break; default: /* We could return -1 here, but examination of the FFmpeg * code shows that return code testing is erratic, so we'll * take charge and complain loudly if FFmpeg/LibAV's API * changes. This way we don't end up chasing phantoms. */ fatal ("Unexpected FFmpeg lock request received: %d", op); } return result; } #endif /* Here we attempt to determine if FFmpeg/LibAV has trashed the 'duration' * and 'bit_rate' fields in AVFormatContext for large files. Determining * whether or not they are likely to be valid is imprecise and will vary * depending (at least) on: * * - The file's size, * - The file's codec, * - The number and size of tags, * - The version of FFmpeg/LibAV, and * - Whether it's FFmpeg or LibAV. * * This function represents a best guess. */ static bool is_timing_broken (AVFormatContext *ic) { if (ic->duration < 0 || ic->bit_rate < 0) return true; /* If and when FFmpeg uses the right field for its calculation this * should be self-correcting. */ if (ic->duration < AV_TIME_BASE && !strcmp (ic->iformat->name, "libgme")) return true; /* AAC timing is inaccurate. */ if (!strcmp (ic->iformat->name, "aac")) return true; /* Formats less than 4 GiB should be okay, except those excluded above. */ if (avio_size (ic->pb) < UINT32_MAX) return false; /* WAV files are limited to 4 GiB but that doesn't stop some encoders. */ if (!strcmp (ic->iformat->name, "wav")) return true; if (!strcmp (ic->iformat->name, "au")) return true; return false; } static void ffmpeg_init () { #ifndef NDEBUG # ifdef DEBUG av_log_set_level (AV_LOG_INFO); # else av_log_set_level (AV_LOG_ERROR); # endif av_log_set_callback (ffmpeg_log_cb); #endif #if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,10,100) avcodec_register_all (); av_register_all (); #endif supported_extns = lists_strs_new (16); load_audio_extns (supported_extns); load_video_extns (supported_extns); #if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100) int rc = av_lockmgr_register (locking_cb); if (rc < 0) { char buf[128]; av_strerror (rc, buf, sizeof (buf)); fatal ("Lock manager initialisation failed: %s", buf); } #endif } static void ffmpeg_destroy () { #if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100) av_lockmgr_register (NULL); #endif av_log_set_level (AV_LOG_QUIET); ffmpeg_log_repeats (NULL); lists_strs_free (supported_extns); } /* Fill info structure with data from ffmpeg comments. */ static void ffmpeg_info (const char *file_name, struct file_tags *info, const int tags_sel) { int err; AVFormatContext *ic = NULL; AVDictionaryEntry *entry; AVDictionary *md; err = avformat_open_input (&ic, file_name, NULL, NULL); if (err < 0) { log_errno ("avformat_open_input() failed", err); return; } err = avformat_find_stream_info (ic, NULL); if (err < 0) { log_errno ("avformat_find_stream_info() failed", err); goto end; } if (!is_timing_broken (ic) && tags_sel & TAGS_TIME) { info->time = -1; if (ic->duration != (int64_t)AV_NOPTS_VALUE && ic->duration >= 0) info->time = ic->duration / AV_TIME_BASE; } if (!(tags_sel & TAGS_COMMENTS)) goto end; md = ic->metadata; if (md == NULL) { unsigned int audio_ix; audio_ix = find_first_audio (ic); if (audio_ix < ic->nb_streams) md = ic->streams[audio_ix]->metadata; } if (md == NULL) { debug ("no metadata found"); goto end; } entry = av_dict_get (md, "track", NULL, 0); if (entry && entry->value && entry->value[0]) info->track = atoi (entry->value); entry = av_dict_get (md, "title", NULL, 0); if (entry && entry->value && entry->value[0]) info->title = xstrdup (entry->value); entry = av_dict_get (md, "artist", NULL, 0); if (entry && entry->value && entry->value[0]) info->artist = xstrdup (entry->value); entry = av_dict_get (md, "album", NULL, 0); if (entry && entry->value && entry->value[0]) info->album = xstrdup (entry->value); end: avformat_close_input (&ic); ffmpeg_log_repeats (NULL); } static long fmt_from_sample_fmt (struct ffmpeg_data *data) { long result; switch (data->enc->sample_fmt) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: result = SFMT_U8; break; case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P: result = SFMT_S16; break; case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P: result = SFMT_S32; break; case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP: result = SFMT_FLOAT; break; default: result = 0; } return result; } /* Try to figure out if seeking is broken for this format. * The aim here is to try and ensure that seeking either works * properly or (because of FFmpeg breakages) is disabled. */ static bool is_seek_broken (struct ffmpeg_data *data) { #if 0 /* FFmpeg's alternate strategy for formats which don't * support seeking natively seems to be... unreliable. */ if (!data->ic->iformat->read_seek) { debug ("Seek broken by AVInputFormat.read_seek"); return true; } #endif /* How much do we trust this? */ if (!(data->ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) { debug ("Seek broken by AVIOContext.seekable"); return true; } #if !SEEK_IN_DECODER /* FLV (.flv): av_seek_frame always returns an error (even on success). * Seeking from the decoder works for false errors (but * probably not for real ones) because the player doesn't * get to see them. */ # ifdef HAVE_FFMPEG if (avcodec_version () < AV_VERSION_INT(55,8,100)) # else if (avcodec_version () < AV_VERSION_INT(55,57,1)) # endif { if (!strcmp (data->ic->iformat->name, "flv")) return true; } #endif return false; } /* Downmix multi-channel audios to stereo. */ static void set_downmixing (struct ffmpeg_data *data) { if (av_get_channel_layout_nb_channels (data->enc->channel_layout) <= 2) return; data->enc->request_channel_layout = AV_CH_LAYOUT_STEREO; } static int ffmpeg_io_read_cb (void *s, uint8_t *buf, int count) { int len; if (!buf || count <= 0) return AVERROR(EINVAL); len = io_read ((struct io_stream *)s, buf, (size_t)count); if (len == 0) len = AVERROR_EOF; else if (len < 0) len = AVERROR(EIO); return len; } static int64_t ffmpeg_io_seek_cb (void *s, int64_t offset, int whence) { int w; int64_t result = -1; /* Warning: Do not blindly accept the avio.h comments for AVSEEK_FORCE * and AVSEEK_SIZE; they are incorrect for later FFmpeg/LibAV * versions. */ w = whence & ~AVSEEK_FORCE; switch (w) { case SEEK_SET: case SEEK_CUR: case SEEK_END: result = io_seek ((struct io_stream *)s, offset, w); break; case AVSEEK_SIZE: result = io_file_size ((struct io_stream *)s); break; } return result; } static struct ffmpeg_data *ffmpeg_make_data (void) { struct ffmpeg_data *data; data = (struct ffmpeg_data *)xmalloc (sizeof (struct ffmpeg_data)); data->ic = NULL; data->pb = NULL; data->stream = NULL; data->enc = NULL; data->codec = NULL; data->remain_buf = NULL; data->remain_buf_len = 0; data->delay = false; data->eof = false; data->eos = false; data->okay = false; data->filename = NULL; data->iostream = NULL; decoder_error_init (&data->error); data->fmt = 0; data->sample_width = 0; data->bitrate = 0; data->avg_bitrate = 0; #if SEEK_IN_DECODER data->seek_req = false; data->seek_sec = 0; #endif data->seek_broken = false; data->timing_broken = false; #if SEEK_IN_DECODER && defined(DEBUG) data->thread_id = 0; #endif return data; } static void *ffmpeg_open_internal (struct ffmpeg_data *data) { int err; const char *extn = NULL; unsigned int audio_ix; data->ic = avformat_alloc_context (); if (!data->ic) fatal ("Can't allocate format context!"); data->ic->pb = avio_alloc_context (NULL, 0, 0, data->iostream, ffmpeg_io_read_cb, NULL, ffmpeg_io_seek_cb); if (!data->ic->pb) fatal ("Can't allocate avio context!"); /* Save AVIO context pointer so we can workaround an FFmpeg * memory leak later in ffmpeg_close(). */ data->pb = data->ic->pb; err = avformat_open_input (&data->ic, NULL, NULL, NULL); if (err < 0) { char *buf = ffmpeg_strerror (err); decoder_error (&data->error, ERROR_FATAL, 0, "Can't open audio: %s", buf); free (buf); return data; } /* When FFmpeg and LibAV misidentify a file's codec (and they do) * then hopefully this will save MOC from wanton destruction. */ if (data->filename) { extn = ext_pos (data->filename); if (extn && !strcasecmp (extn, "wav") && strcmp (data->ic->iformat->name, "wav")) { decoder_error (&data->error, ERROR_FATAL, 0, "Format possibly misidentified " "as '%s' by FFmpeg/LibAV", data->ic->iformat->name); goto end; } } err = avformat_find_stream_info (data->ic, NULL); if (err < 0) { /* Depending on the particular FFmpeg/LibAV version in use, this * may misreport experimental codecs. Given we don't know the * codec at this time, we will have to live with it. */ char *buf = ffmpeg_strerror (err); decoder_error (&data->error, ERROR_FATAL, 0, "Could not find codec parameters: %s", buf); free (buf); goto end; } audio_ix = find_first_audio (data->ic); if (audio_ix == data->ic->nb_streams) { decoder_error (&data->error, ERROR_FATAL, 0, "No audio in source"); goto end; } data->stream = data->ic->streams[audio_ix]; data->enc = avcodec_alloc_context3 (NULL); if (!data->enc) { decoder_error (&data->error, ERROR_FATAL, 0, "Failed to allocate codec context"); goto end; } #ifdef HAVE_STRUCT_AVSTREAM_CODECPAR err = avcodec_parameters_to_context (data->enc, data->stream->codecpar); if (err < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Failed to copy codec parameters"); goto end; } #else err = avcodec_copy_context (data->enc, data->stream->codec); if (err < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Failed to copy codec context"); goto end; } #endif data->codec = avcodec_find_decoder (data->enc->codec_id); if (!data->codec) { decoder_error (&data->error, ERROR_FATAL, 0, "No codec for this audio"); goto end; } if (data->filename) { const char *fn; fn = strrchr (data->filename, '/'); fn = fn ? fn + 1 : data->filename; debug ("FFmpeg thinks '%s' is format(codec) '%s(%s)'", fn, data->ic->iformat->name, data->codec->name); } else debug ("FFmpeg thinks stream is format(codec) '%s(%s)'", data->ic->iformat->name, data->codec->name); /* This may or may not work depending on the particular version of * FFmpeg/LibAV in use. For some versions this will be caught in * *_find_stream_info() above and misreported as an unfound codec * parameters error. */ if (data->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) { decoder_error (&data->error, ERROR_FATAL, 0, "The codec is experimental and may damage MOC: %s", data->codec->name); goto end; } set_downmixing (data); if (data->codec->capabilities & AV_CODEC_CAP_TRUNCATED) data->enc->flags |= AV_CODEC_FLAG_TRUNCATED; if (avcodec_open2 (data->enc, data->codec, NULL) < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "No codec for this audio"); goto end; } data->fmt = fmt_from_sample_fmt (data); if (data->fmt == 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Cannot get sample size from unknown sample format: %s", av_get_sample_fmt_name (data->enc->sample_fmt)); goto end; } data->sample_width = sfmt_Bps (data->fmt); if (data->codec->capabilities & AV_CODEC_CAP_DELAY) data->delay = true; data->seek_broken = is_seek_broken (data); data->timing_broken = is_timing_broken (data->ic); if (data->timing_broken && extn && !strcasecmp (extn, "wav")) { ffmpeg_log_repeats (NULL); decoder_error (&data->error, ERROR_FATAL, 0, "Broken WAV file; use W64!"); goto end; } data->okay = true; if (!data->timing_broken && data->ic->duration >= AV_TIME_BASE) data->avg_bitrate = (int) (avio_size (data->ic->pb) / (data->ic->duration / AV_TIME_BASE) * 8); if (!data->timing_broken && data->ic->bit_rate > 0) data->bitrate = data->ic->bit_rate; return data; end: #ifdef HAVE_AVCODEC_FREE_CONTEXT avcodec_free_context (&data->enc); #else avcodec_close (data->enc); av_freep (&data->enc); #endif avformat_close_input (&data->ic); ffmpeg_log_repeats (NULL); return data; } static void *ffmpeg_open (const char *file) { struct ffmpeg_data *data; data = ffmpeg_make_data (); data->filename = xstrdup (file); data->iostream = io_open (file, 1); if (!io_ok (data->iostream)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open file: %s", io_strerror (data->iostream)); return data; } return ffmpeg_open_internal (data); } static void *ffmpeg_open_stream (struct io_stream *stream) { struct ffmpeg_data *data; data = ffmpeg_make_data (); data->iostream = stream; return ffmpeg_open_internal (data); } static int ffmpeg_can_decode (struct io_stream *stream) { int res; AVProbeData probe_data; AVInputFormat *fmt; char buf[8096 + AVPROBE_PADDING_SIZE] = {0}; res = io_peek (stream, buf, sizeof (buf)); if (res < 0) { error ("Stream error: %s", io_strerror (stream)); return 0; } probe_data.filename = NULL; probe_data.buf = (unsigned char*)buf; probe_data.buf_size = sizeof (buf) - AVPROBE_PADDING_SIZE; #ifdef HAVE_STRUCT_AVPROBEDATA_MIME_TYPE probe_data.mime_type = NULL; #endif fmt = av_probe_input_format (&probe_data, 1); return fmt != NULL; } static void put_in_remain_buf (struct ffmpeg_data *data, const char *buf, const int len) { debug ("Remain: %dB", len); data->remain_buf_len = len; data->remain_buf = (char *)xmalloc (len); memcpy (data->remain_buf, buf, len); } static void add_to_remain_buf (struct ffmpeg_data *data, const char *buf, const int len) { debug ("Adding %dB to remain_buf", len); data->remain_buf = (char *)xrealloc (data->remain_buf, data->remain_buf_len + len); memcpy (data->remain_buf + data->remain_buf_len, buf, len); data->remain_buf_len += len; debug ("remain_buf is %dB long", data->remain_buf_len); } /* Free the remainder buffer. */ static void free_remain_buf (struct ffmpeg_data *data) { free (data->remain_buf); data->remain_buf = NULL; data->remain_buf_len = 0; } /* Satisfy the request from previously decoded samples. */ static int take_from_remain_buf (struct ffmpeg_data *data, char *buf, int buf_len) { int to_copy = MIN (buf_len, data->remain_buf_len); debug ("Copying %d bytes from the remain buf", to_copy); memcpy (buf, data->remain_buf, to_copy); if (to_copy < data->remain_buf_len) { memmove (data->remain_buf, data->remain_buf + to_copy, data->remain_buf_len - to_copy); data->remain_buf_len -= to_copy; } else { debug ("Remain buf is now empty"); free_remain_buf (data); } return to_copy; } /* Copy samples to output or remain buffer. */ static int copy_or_buffer (struct ffmpeg_data *data, char *in, int in_len, char *out, int out_len) { if (in_len == 0) return 0; if (in_len <= out_len) { memcpy (out, in, in_len); return in_len; } if (out_len == 0) { add_to_remain_buf (data, in, in_len); return 0; } memcpy (out, in, out_len); put_in_remain_buf (data, in + out_len, in_len - out_len); return out_len; } /* Create a new packet ('cause FFmpeg doesn't provide one). */ static inline AVPacket *new_packet (struct ffmpeg_data *data) { AVPacket *pkt; assert (data->stream); #if HAVE_AV_PACKET_FNS pkt = av_packet_alloc (); #else pkt = (AVPacket *)av_malloc (sizeof (AVPacket)); if (!pkt) fatal ("av_malloc() failed to allocate memory"); av_init_packet (pkt); pkt->data = NULL; pkt->size = 0; #endif pkt->stream_index = data->stream->index; return pkt; } static inline void free_packet (AVPacket *pkt) { #if HAVE_AV_PACKET_FNS av_packet_free (&pkt); #else av_free_packet (pkt); av_free (pkt); #endif } /* Read a packet from the file or empty packet if flushing delayed * samples. */ static AVPacket *get_packet (struct ffmpeg_data *data) { int rc; AVPacket *pkt; assert (data); assert (!data->eos); pkt = new_packet (data); if (data->eof) return pkt; rc = av_read_frame (data->ic, pkt); if (rc >= 0) { debug ("Got %dB packet", pkt->size); return pkt; } free_packet (pkt); /* FFmpeg has (at least) two ways of indicating EOF. (Awesome!) */ if (rc == AVERROR_EOF) data->eof = true; if (data->ic->pb && data->ic->pb->eof_reached) data->eof = true; if (!data->eof && rc < 0) { char *buf = ffmpeg_strerror (rc); decoder_error (&data->error, ERROR_FATAL, 0, "Error in the stream: %s", buf); free (buf); return NULL; } if (data->delay) return new_packet (data); data->eos = true; return NULL; } #ifndef HAVE_AVCODEC_RECEIVE_FRAME /* Decode samples from packet data using avcodec_decode_audio4(). */ # define decode_audio avcodec_decode_audio4 #endif #ifdef HAVE_AVCODEC_RECEIVE_FRAME /* Decode audio using FFmpeg's send/receive encoding and decoding API. */ static int decode_audio (AVCodecContext *ctx, AVFrame *frame, int *got_frame_ptr, const AVPacket *pkt) { int rc, result = 0; *got_frame_ptr = 0; rc = avcodec_send_packet (ctx, pkt); switch (rc) { case 0: break; case AVERROR(EAGAIN): debug ("avcodec_send_packet(): AVERROR(EAGAIN)"); break; case AVERROR_EOF: if (pkt->data) debug ("avcodec_send_packet(): AVERROR_EOF"); break; case AVERROR(EINVAL): logit ("avcodec_send_packet(): AVERROR(EINVAL)"); result = rc; break; case AVERROR(ENOMEM): logit ("avcodec_send_packet(): AVERROR(ENOMEM)"); result = rc; break; default: log_errno ("avcodec_send_packet()", rc); result = rc; } if (result == 0) { result = pkt->size; rc = avcodec_receive_frame (ctx, frame); switch (rc) { case 0: *got_frame_ptr = 1; break; case AVERROR(EAGAIN): debug ("avcodec_receive_frame(): AVERROR(EAGAIN)"); break; case AVERROR_EOF: debug ("avcodec_receive_frame(): AVERROR_EOF"); avcodec_flush_buffers (ctx); break; case AVERROR(EINVAL): logit ("avcodec_receive_frame(): AVERROR(EINVAL)"); result = rc; break; default: log_errno ("avcodec_receive_frame()", rc); result = rc; } } return result; } #endif /* Decode samples from packet data. */ static int decode_packet (struct ffmpeg_data *data, AVPacket *pkt, char *buf, int buf_len) { int filled = 0; char *packed; AVFrame *frame; #ifdef HAVE_AV_FRAME_FNS frame = av_frame_alloc (); #else frame = avcodec_alloc_frame (); #endif do { int len, got_frame, is_planar, packed_size, copied; len = decode_audio (data->enc, frame, &got_frame, pkt); if (len < 0) { /* skip frame */ decoder_error (&data->error, ERROR_STREAM, 0, "Error in the stream!"); break; } debug ("Decoded %dB", len); pkt->data += len; pkt->size -= len; if (!got_frame) { data->eos = data->eof && (pkt->size == 0); continue; } if (frame->nb_samples == 0) continue; is_planar = av_sample_fmt_is_planar (data->enc->sample_fmt); packed = (char *)frame->extended_data[0]; packed_size = frame->nb_samples * data->sample_width * data->enc->channels; if (is_planar && data->enc->channels > 1) { int sample, ch; packed = xmalloc (packed_size); for (sample = 0; sample < frame->nb_samples; sample += 1) { for (ch = 0; ch < data->enc->channels; ch += 1) memcpy (packed + (sample * data->enc->channels + ch) * data->sample_width, (char *)frame->extended_data[ch] + sample * data->sample_width, data->sample_width); } } copied = copy_or_buffer (data, packed, packed_size, buf, buf_len); buf += copied; filled += copied; buf_len -= copied; debug ("Copying %dB (%dB filled)", packed_size, filled); if (packed != (char *)frame->extended_data[0]) free (packed); } while (pkt->size > 0); #ifdef HAVE_AV_FRAME_FNS av_frame_free (&frame); #else avcodec_free_frame (&frame); #endif return filled; } #if SEEK_IN_DECODER static bool seek_in_stream (struct ffmpeg_data *data) #else static bool seek_in_stream (struct ffmpeg_data *data, int sec) #endif { int rc, flags = AVSEEK_FLAG_ANY; int64_t seek_ts; #if SEEK_IN_DECODER int sec = data->seek_sec; #ifdef DEBUG assert (pthread_equal (data->thread_id, pthread_self ())); #endif #endif /* FFmpeg can't seek if the file has already reached EOF. */ if (data->eof) return false; seek_ts = av_rescale (sec, data->stream->time_base.den, data->stream->time_base.num); if (data->stream->start_time != (int64_t)AV_NOPTS_VALUE) { if (seek_ts > INT64_MAX - MAX(0, data->stream->start_time)) { logit ("Seek value too large"); return false; } seek_ts += data->stream->start_time; } if (data->stream->cur_dts > seek_ts) flags |= AVSEEK_FLAG_BACKWARD; rc = av_seek_frame (data->ic, data->stream->index, seek_ts, flags); if (rc < 0) { log_errno ("Seek error", rc); return false; } avcodec_flush_buffers (data->enc); return true; } static inline int compute_bitrate (struct sound_params *sound_params, int bytes_used, int bytes_produced, int bitrate) { int64_t bytes_per_frame, bytes_per_second, seconds; bytes_per_frame = sfmt_Bps (sound_params->fmt) * sound_params->channels; bytes_per_second = bytes_per_frame * (int64_t)sound_params->rate; seconds = (int64_t)bytes_produced / bytes_per_second; if (seconds > 0) bitrate = (int)((int64_t)bytes_used * 8 / seconds); return bitrate; } static int ffmpeg_decode (void *prv_data, char *buf, int buf_len, struct sound_params *sound_params) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; int bytes_used = 0, bytes_produced = 0; decoder_error_clear (&data->error); if (data->eos) return 0; /* FFmpeg claims to always return native endian. */ sound_params->channels = data->enc->channels; sound_params->rate = data->enc->sample_rate; sound_params->fmt = data->fmt | SFMT_NE; #if SEEK_IN_DECODER if (data->seek_req) { data->seek_req = false; if (seek_in_stream (data)) free_remain_buf (data); } #endif if (data->remain_buf) return take_from_remain_buf (data, buf, buf_len); do { uint8_t *saved_pkt_data_ptr; AVPacket *pkt; pkt = get_packet (data); if (!pkt) break; if (pkt->stream_index != data->stream->index) { free_packet (pkt); continue; } #ifdef AV_PKT_FLAG_CORRUPT if (pkt->flags & AV_PKT_FLAG_CORRUPT) { ffmpeg_log_repeats (NULL); debug ("Dropped corrupt packet."); free_packet (pkt); continue; } #endif saved_pkt_data_ptr = pkt->data; bytes_used += pkt->size; bytes_produced = decode_packet (data, pkt, buf, buf_len); buf += bytes_produced; buf_len -= bytes_produced; /* FFmpeg will segfault if the data pointer is not restored. */ pkt->data = saved_pkt_data_ptr; free_packet (pkt); } while (!bytes_produced && !data->eos); if (!data->timing_broken) data->bitrate = compute_bitrate (sound_params, bytes_used, bytes_produced + data->remain_buf_len, data->bitrate); return bytes_produced; } static int ffmpeg_seek (void *prv_data, int sec) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; assert (sec >= 0); if (data->seek_broken) return -1; #if SEEK_IN_DECODER data->seek_sec = sec; data->seek_req = true; #ifdef DEBUG data->thread_id = pthread_self (); #endif #else if (!seek_in_stream (data, sec)) return -1; free_remain_buf (data); #endif return sec; } static void ffmpeg_close (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; /* We need to delve into the AVIOContext struct to free the * buffer FFmpeg leaked if avformat_open_input() failed. Do * not be tempted to call avio_close() here; it will segfault. */ if (data->pb) { av_freep (&data->pb->buffer); av_freep (&data->pb); } if (data->okay) { #ifdef HAVE_AVCODEC_FREE_CONTEXT avcodec_free_context (&data->enc); #else avcodec_close (data->enc); av_freep (&data->enc); #endif avformat_close_input (&data->ic); free_remain_buf (data); } ffmpeg_log_repeats (NULL); if (data->iostream) { io_close (data->iostream); data->iostream = NULL; } decoder_error_clear (&data->error); free (data->filename); free (data); } static struct io_stream *ffmpeg_get_iostream (void *prv_data) { struct ffmpeg_data *data; assert (prv_data); data = (struct ffmpeg_data *)prv_data; return data->iostream; } static int ffmpeg_get_bitrate (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; return data->timing_broken ? -1 : data->bitrate / 1000; } static int ffmpeg_get_avg_bitrate (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; return data->timing_broken ? -1 : data->avg_bitrate / 1000; } static int ffmpeg_get_duration (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; if (data->timing_broken) return -1; if (!data->stream) return -1; if (data->stream->duration == (int64_t)AV_NOPTS_VALUE) return -1; if (data->stream->duration < 0) return -1; return data->stream->duration * data->stream->time_base.num / data->stream->time_base.den; } static int ffmpeg_our_format_ext (const char *ext) { return (lists_strs_exists (supported_extns, ext)) ? 1 : 0; } static int ffmpeg_our_format_mime (const char *mime_type) { AVOutputFormat *fmt; fmt = av_guess_format (NULL, NULL, mime_type); return fmt ? 1 : 0; } static void ffmpeg_get_error (void *prv_data, struct decoder_error *error) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; decoder_error_copy (error, &data->error); } static struct decoder ffmpeg_decoder = { DECODER_API_VERSION, ffmpeg_init, ffmpeg_destroy, ffmpeg_open, ffmpeg_open_stream, ffmpeg_can_decode, ffmpeg_close, ffmpeg_decode, ffmpeg_seek, ffmpeg_info, ffmpeg_get_bitrate, ffmpeg_get_duration, ffmpeg_get_error, ffmpeg_our_format_ext, ffmpeg_our_format_mime, NULL, NULL, ffmpeg_get_iostream, ffmpeg_get_avg_bitrate }; struct decoder *plugin_init () { return &ffmpeg_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/ffmpeg/ffmpeg.m40000664000076400000500000000473613327433144021265 0ustar riesebiesrcdnl ffmpeg/libav AC_ARG_WITH(ffmpeg, AS_HELP_STRING([--without-ffmpeg], [Compile without ffmpeg/libav])) if test "x$with_ffmpeg" != "xno" then PKG_CHECK_MODULES(ffmpeg, libavutil libavcodec libavformat, [ffmpeg_CPPFLAGS=`$PKG_CONFIG --cflags-only-I libavutil libavcodec libavformat` AC_SUBST(ffmpeg_CPPFLAGS) AC_SUBST(ffmpeg_CFLAGS) AC_SUBST(ffmpeg_LIBS) want_ffmpeg="yes"], [true]) if test "x$want_ffmpeg" = "xyes" then if $PKG_CONFIG --max-version 53.47.99 libavcodec then AC_MSG_ERROR([You need FFmpeg/LibAV of at least release 1.0/10.0.]) fi if test "`$PKG_CONFIG --modversion libavcodec | awk -F. '{ print $3; }'`" -gt 99 then if ! $PKG_CONFIG --atleast-version 54.59.100 libavcodec then AC_MSG_ERROR([You need FFmpeg of at least release 1.0.]) fi DECODER_PLUGINS="$DECODER_PLUGINS ffmpeg" AC_DEFINE([HAVE_FFMPEG], 1, [Define to 1 if you know you have FFmpeg.]) else if ! $PKG_CONFIG --atleast-version 55.34.1 libavcodec then AC_MSG_ERROR([You need LibAV of at least release 10.0.]) fi DECODER_PLUGINS="$DECODER_PLUGINS ffmpeg(libav)" AC_DEFINE([HAVE_LIBAV], 1, [Define to 1 if you know you have LibAV.]) fi save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $ffmpeg_CPPFLAGS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $ffmpeg_CFLAGS" save_LIBS="$LIBS" LIBS="$LIBS $ffmpeg_LIBS" AC_CHECK_MEMBERS([struct AVProbeData.mime_type], [], [], [#include ]) AC_CHECK_HEADERS([libavutil/channel_layout.h]) AC_SEARCH_LIBS(av_packet_alloc, avcodec, [AC_DEFINE([HAVE_AV_PACKET_FNS], 1, [Define to 1 if you have the `av_packet_*' functions.])]) AC_SEARCH_LIBS(av_frame_alloc, avutil, [AC_DEFINE([HAVE_AV_FRAME_FNS], 1, [Define to 1 if you have the `av_frame_*' functions.])]) AC_SEARCH_LIBS(avcodec_free_context, avcodec, [AC_DEFINE([HAVE_AVCODEC_FREE_CONTEXT], 1, [Define to 1 if you have the `avcodec_free_context' function.])]) AC_SEARCH_LIBS(avcodec_receive_frame, avcodec, [AC_DEFINE([HAVE_AVCODEC_RECEIVE_FRAME], 1, [Define to 1 if you have the `avcodec_receive_frame' function.])]) AC_CHECK_MEMBERS([struct AVStream.codecpar], [], [], [#include ]) CPPFLAGS="$save_CPPFLAGS" CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" fi fi AM_CONDITIONAL([BUILD_ffmpeg], [test "$want_ffmpeg"]) AC_CONFIG_FILES([decoder_plugins/ffmpeg/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/flac/0002775000076400000500000000000013710016723017204 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/flac/Makefile.am0000664000076400000500000000041510204714722021235 0ustar riesebiesrclib_LTLIBRARIES = libflac_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libflac_decoder_la_CFLAGS = $(LIBFLAC_CFLAGS) -I$(top_srcdir) libflac_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libflac_decoder_la_LIBADD = $(LIBFLAC_LIBS) libflac_decoder_la_SOURCES = flac.c moc-2.6.0~svn-r3005/decoder_plugins/flac/flac.c0000664000076400000500000003427412716252773020301 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ /* The code is based on libxmms-flac written by Josh Coalson. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include /*#define DEBUG*/ #include "common.h" #include "audio.h" #include "decoder.h" #include "server.h" #include "log.h" #include "io.h" #define MAX_SUPPORTED_CHANNELS 2 #define SAMPLES_PER_WRITE 512 #define SAMPLE_BUFFER_SIZE ((FLAC__MAX_BLOCK_SIZE + SAMPLES_PER_WRITE) * MAX_SUPPORTED_CHANNELS * (32/8)) struct flac_data { FLAC__StreamDecoder *decoder; struct io_stream *stream; int bitrate; int avg_bitrate; int abort; /* abort playing (due to an error) */ unsigned int length; FLAC__uint64 total_samples; FLAC__byte sample_buffer[SAMPLE_BUFFER_SIZE]; unsigned int sample_buffer_fill; /* sound parameters */ unsigned int bits_per_sample; unsigned int sample_rate; unsigned int channels; FLAC__uint64 last_decode_position; int ok; /* was this stream successfully opened? */ struct decoder_error error; }; /* Convert FLAC big-endian data into PCM little-endian. */ static size_t pack_pcm_signed (FLAC__byte *data, const FLAC__int32 * const input[], unsigned int wide_samples, unsigned int channels, unsigned int bps) { FLAC__byte * const start = data; FLAC__int32 sample; const FLAC__int32 *input_; unsigned int samples, channel; unsigned int bytes_per_sample; unsigned int incr; if (bps == 24) bps = 32; /* we encode to 32-bit words */ bytes_per_sample = bps / 8; incr = bytes_per_sample * channels; for (channel = 0; channel < channels; channel++) { samples = wide_samples; data = start + bytes_per_sample * channel; input_ = input[channel]; while(samples--) { sample = *input_++; switch(bps) { case 8: data[0] = sample; break; case 16: data[1] = (FLAC__byte)(sample >> 8); data[0] = (FLAC__byte)sample; break; case 32: data[3] = (FLAC__byte)(sample >> 16); data[2] = (FLAC__byte)(sample >> 8); data[1] = (FLAC__byte)sample; data[0] = 0; break; } data += incr; } } debug ("Converted %u bytes", wide_samples * channels * bytes_per_sample); return wide_samples * channels * bytes_per_sample; } static FLAC__StreamDecoderWriteStatus write_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) { struct flac_data *data = (struct flac_data *)client_data; const unsigned int wide_samples = frame->header.blocksize; if (data->abort) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; data->sample_buffer_fill = pack_pcm_signed ( data->sample_buffer, buffer, wide_samples, data->channels, data->bits_per_sample); return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } static void metadata_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, const FLAC__StreamMetadata *metadata, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { debug ("Got metadata info"); data->total_samples = metadata->data.stream_info.total_samples; data->bits_per_sample = metadata->data.stream_info.bits_per_sample; data->channels = metadata->data.stream_info.channels; data->sample_rate = metadata->data.stream_info.sample_rate; if (data->total_samples > 0) data->length = data->total_samples / data->sample_rate; } } static void error_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, FLAC__StreamDecoderErrorStatus status, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; if (status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC) { debug ("Aborting due to error"); data->abort = 1; } else decoder_error (&data->error, ERROR_FATAL, 0, "FLAC: lost sync"); } static FLAC__StreamDecoderReadStatus read_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, FLAC__byte buffer[], size_t *bytes, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; ssize_t res; res = io_read (data->stream, buffer, *bytes); if (res > 0) { *bytes = res; return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } if (res == 0) { *bytes = 0; /* not sure why this works, but if it ain't broke... */ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; } error ("read error: %s", io_strerror(data->stream)); return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } static FLAC__StreamDecoderSeekStatus seek_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, FLAC__uint64 absolute_byte_offset, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; return io_seek(data->stream, absolute_byte_offset, SEEK_SET) >= 0 ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; } static FLAC__StreamDecoderTellStatus tell_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, FLAC__uint64 *absolute_byte_offset, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; *absolute_byte_offset = io_tell (data->stream); return FLAC__STREAM_DECODER_TELL_STATUS_OK; } static FLAC__StreamDecoderLengthStatus length_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, FLAC__uint64 *stream_length, void *client_data) { off_t file_size; struct flac_data *data = (struct flac_data *)client_data; file_size = io_file_size (data->stream); if (file_size == -1) return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; *stream_length = file_size; return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } static FLAC__bool eof_cb ( const FLAC__StreamDecoder *unused ATTR_UNUSED, void *client_data) { struct flac_data *data = (struct flac_data *)client_data; return io_eof (data->stream); } static void *flac_open_internal (const char *file, const int buffered) { struct flac_data *data; data = (struct flac_data *)xmalloc (sizeof(struct flac_data)); decoder_error_init (&data->error); data->decoder = NULL; data->bitrate = -1; data->avg_bitrate = -1; data->abort = 0; data->sample_buffer_fill = 0; data->last_decode_position = 0; data->length = -1; data->ok = 0; data->stream = io_open (file, buffered); if (!io_ok(data->stream)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't load file: %s", io_strerror(data->stream)); return data; } if (!(data->decoder = FLAC__stream_decoder_new())) { decoder_error (&data->error, ERROR_FATAL, 0, "FLAC__stream_decoder_new() failed"); return data; } FLAC__stream_decoder_set_md5_checking (data->decoder, false); FLAC__stream_decoder_set_metadata_ignore_all (data->decoder); FLAC__stream_decoder_set_metadata_respond (data->decoder, FLAC__METADATA_TYPE_STREAMINFO); if (FLAC__stream_decoder_init_stream(data->decoder, read_cb, seek_cb, tell_cb, length_cb, eof_cb, write_cb, metadata_cb, error_cb, data) != FLAC__STREAM_DECODER_INIT_STATUS_OK) { decoder_error (&data->error, ERROR_FATAL, 0, "FLAC__stream_decoder_init() failed"); return data; } if (!FLAC__stream_decoder_process_until_end_of_metadata(data->decoder)) { decoder_error (&data->error, ERROR_FATAL, 0, "FLAC__stream_decoder_process_until_end_of_metadata() failed."); return data; } data->ok = 1; if (data->length > 0) { off_t data_size = io_file_size (data->stream); if (data_size > 0) { FLAC__uint64 pos; if (FLAC__stream_decoder_get_decode_position (data->decoder, &pos)) data_size -= pos; data->avg_bitrate = data_size * 8 / data->length; } } return data; } static void *flac_open (const char *file) { return flac_open_internal (file, 1); } static void flac_close (void *void_data) { struct flac_data *data = (struct flac_data *)void_data; if (data->decoder) { FLAC__stream_decoder_finish (data->decoder); FLAC__stream_decoder_delete (data->decoder); } io_close (data->stream); decoder_error_clear (&data->error); free (data); } static void fill_tag (FLAC__StreamMetadata_VorbisComment_Entry *comm, struct file_tags *tags) { char *name, *value; FLAC__byte *eq; int value_length; eq = memchr (comm->entry, '=', comm->length); if (!eq) return; name = (char *)xmalloc (sizeof(char) * (eq - comm->entry + 1)); strncpy (name, (char *)comm->entry, eq - comm->entry); name[eq - comm->entry] = 0; value_length = comm->length - (eq - comm->entry + 1); if (value_length == 0) { free (name); return; } value = (char *)xmalloc (sizeof(char) * (value_length + 1)); strncpy (value, (char *)(eq + 1), value_length); value[value_length] = 0; if (!strcasecmp(name, "title")) tags->title = value; else if (!strcasecmp(name, "artist")) tags->artist = value; else if (!strcasecmp(name, "album")) tags->album = value; else if (!strcasecmp(name, "tracknumber") || !strcasecmp(name, "track")) { tags->track = atoi (value); free (value); } else free (value); free (name); } static void get_vorbiscomments (const char *filename, struct file_tags *tags) { FLAC__Metadata_SimpleIterator *iterator = FLAC__metadata_simple_iterator_new(); FLAC__bool got_vorbis_comments = false; debug ("Reading comments for %s", filename); if (!iterator) { logit ("FLAC__metadata_simple_iterator_new() failed."); return; } if (!FLAC__metadata_simple_iterator_init(iterator, filename, true, true)) { logit ("FLAC__metadata_simple_iterator_init failed."); FLAC__metadata_simple_iterator_delete(iterator); return; } do { if (FLAC__metadata_simple_iterator_get_block_type(iterator) == FLAC__METADATA_TYPE_VORBIS_COMMENT) { FLAC__StreamMetadata *block; block = FLAC__metadata_simple_iterator_get_block ( iterator); if (block) { unsigned int i; const FLAC__StreamMetadata_VorbisComment *vc = &block->data.vorbis_comment; for (i = 0; i < vc->num_comments; i++) fill_tag (&vc->comments[i], tags); FLAC__metadata_object_delete (block); got_vorbis_comments = true; } } } while (!got_vorbis_comments && FLAC__metadata_simple_iterator_next(iterator)); FLAC__metadata_simple_iterator_delete(iterator); } static void flac_info (const char *file_name, struct file_tags *info, const int tags_sel) { if (tags_sel & TAGS_TIME) { struct flac_data *data; data = flac_open_internal (file_name, 0); if (data->ok) info->time = data->length; flac_close (data); } if (tags_sel & TAGS_COMMENTS) get_vorbiscomments (file_name, info); } static int flac_seek (void *void_data, int sec) { struct flac_data *data = (struct flac_data *)void_data; FLAC__uint64 target_sample; if ((unsigned int)sec > data->length) return -1; target_sample = (FLAC__uint64)(((double)sec / (double)data->length) * (double)data->total_samples); if (FLAC__stream_decoder_seek_absolute(data->decoder, target_sample)) return sec; logit ("FLAC__stream_decoder_seek_absolute() failed."); return -1; } static int flac_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct flac_data *data = (struct flac_data *)void_data; unsigned int to_copy; int bytes_per_sample; FLAC__uint64 decode_position; bytes_per_sample = data->bits_per_sample / 8; switch (bytes_per_sample) { case 1: sound_params->fmt = SFMT_S8; break; case 2: sound_params->fmt = SFMT_S16 | SFMT_LE; break; case 3: sound_params->fmt = SFMT_S32 | SFMT_LE; break; } sound_params->rate = data->sample_rate; sound_params->channels = data->channels; decoder_error_clear (&data->error); if (!data->sample_buffer_fill) { debug ("decoding..."); if (FLAC__stream_decoder_get_state(data->decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) { logit ("EOF"); return 0; } if (!FLAC__stream_decoder_process_single(data->decoder)) { decoder_error (&data->error, ERROR_FATAL, 0, "Read error processing frame."); return 0; } /* Count the bitrate */ if(!FLAC__stream_decoder_get_decode_position(data->decoder, &decode_position)) decode_position = 0; if (decode_position > data->last_decode_position) { int bytes_per_sec = bytes_per_sample * data->sample_rate * data->channels; data->bitrate = (decode_position - data->last_decode_position) * 8.0 / (data->sample_buffer_fill / (float)bytes_per_sec) / 1000; } data->last_decode_position = decode_position; } else debug ("Some date remain in the buffer."); debug ("Decoded %d bytes", data->sample_buffer_fill); to_copy = MIN((unsigned int)buf_len, data->sample_buffer_fill); memcpy (buf, data->sample_buffer, to_copy); memmove (data->sample_buffer, data->sample_buffer + to_copy, data->sample_buffer_fill - to_copy); data->sample_buffer_fill -= to_copy; return to_copy; } static int flac_get_bitrate (void *void_data) { struct flac_data *data = (struct flac_data *)void_data; return data->bitrate; } static int flac_get_avg_bitrate (void *void_data) { struct flac_data *data = (struct flac_data *)void_data; return data->avg_bitrate / 1000; } static int flac_get_duration (void *void_data) { int result = -1; struct flac_data *data = (struct flac_data *)void_data; if (data->ok) result = data->length; return result; } static void flac_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "FLC"); } static int flac_our_format_ext (const char *ext) { return !strcasecmp (ext, "flac") || !strcasecmp (ext, "fla"); } static int flac_our_format_mime (const char *mime) { return !strcasecmp (mime, "audio/flac") || !strncasecmp (mime, "audio/flac;", 11) || !strcasecmp (mime, "audio/x-flac") || !strncasecmp (mime, "audio/x-flac;", 13); } static void flac_get_error (void *prv_data, struct decoder_error *error) { struct flac_data *data = (struct flac_data *)prv_data; decoder_error_copy (error, &data->error); } static struct decoder flac_decoder = { DECODER_API_VERSION, NULL, NULL, flac_open, NULL, NULL, flac_close, flac_decode, flac_seek, flac_info, flac_get_bitrate, flac_get_duration, flac_get_error, flac_our_format_ext, flac_our_format_mime, flac_get_name, NULL, NULL, flac_get_avg_bitrate }; struct decoder *plugin_init () { return &flac_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/flac/flac.m40000664000076400000500000000070712402500171020345 0ustar riesebiesrcdnl FLAC AC_ARG_WITH(flac, AS_HELP_STRING([--without-flac], [Compile without FLAC support])) if test "x$with_flac" != "xno" then PKG_CHECK_MODULES(LIBFLAC, [flac >= 1.1.3], [AC_SUBST(LIBFLAC_LIBS) AC_SUBST(LIBFLAC_CFLAGS) want_flac="yes" DECODER_PLUGINS="$DECODER_PLUGINS flac"], [true]) fi AM_CONDITIONAL([BUILD_flac], [test "$want_flac"]) AC_CONFIG_FILES([decoder_plugins/flac/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/modplug/0002775000076400000500000000000013710016723017746 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/modplug/Makefile.am0000664000076400000500000000043710417240636022007 0ustar riesebiesrclib_LTLIBRARIES = libmodplug_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libmodplug_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libmodplug_decoder_la_LIBADD = $(modplug_LIBS) libmodplug_decoder_la_CFLAGS = $(modplug_CFLAGS) -I$(top_srcdir) libmodplug_decoder_la_SOURCES = modplug.c moc-2.6.0~svn-r3005/decoder_plugins/modplug/modplug.c0000664000076400000500000002242012713507777021577 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * libmodplug-plugin Copyright (C) 2006 Hendrik Iben * Enables MOC to play modules via libmodplug (actually just a wrapper around * libmodplug's C-wrapper... :-)). * * Based on ideas from G"urkan Seng"un's modplugplay. A command line * interface to the modplugxmms library. * Structure of this plugin is an adaption of the libsndfile-plugin from * moc. * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #define DEBUG #include "common.h" #include "io.h" #include "decoder.h" #include "log.h" #include "files.h" #include "options.h" // Limiting maximum size for loading a module was suggested by Damian. // I've never seen such a large module so this should be a safe limit... #ifndef MAXMODSIZE #define MAXMODSIZE 1024*1024*42 #endif ModPlug_Settings settings; struct modplug_data { ModPlugFile *modplugfile; int length; char *filedata; struct decoder_error error; }; #if !defined(NDEBUG) && defined(DEBUG) // this is needed because debugging in plugin_init gets lost // The alternative is to debug settings when opening a file // but settings never change so I need a flag to check if it // has been done... static int doDebugSettings=1; static void debugSettings(void) { debug("\n\ ModPlug-Settings:\n\ Oversampling : %s\n\ NoiseReduction : %s\n\ Reverb : %s\n\ MegaBass : %s\n\ Surround : %s\n\ ResamplingMode : %s\n\ Channels : %d\n\ Bits : %d\n\ Frequency : %d\n\ ReverbDepth : %d\n\ ReverbDelay : %d\n\ BassAmount : %d\n\ BassRange : %d\n\ SurroundDepth : %d\n\ SurroundDelay : %d\n\ LoopCount : %d", (settings.mFlags & MODPLUG_ENABLE_OVERSAMPLING)?"yes":"no" ,(settings.mFlags & MODPLUG_ENABLE_NOISE_REDUCTION)?"yes":"no" ,(settings.mFlags & MODPLUG_ENABLE_REVERB)?"yes":"no" ,(settings.mFlags & MODPLUG_ENABLE_MEGABASS)?"yes":"no" ,(settings.mFlags & MODPLUG_ENABLE_SURROUND)?"yes":"no" ,(settings.mResamplingMode == MODPLUG_RESAMPLE_FIR)?"8-tap fir": (settings.mResamplingMode == MODPLUG_RESAMPLE_SPLINE)?"spline": (settings.mResamplingMode == MODPLUG_RESAMPLE_LINEAR)?"linear": (settings.mResamplingMode == MODPLUG_RESAMPLE_NEAREST)?"nearest":"?" ,settings.mChannels ,settings.mBits ,settings.mFrequency ,settings.mReverbDepth ,settings.mReverbDelay ,settings.mBassAmount ,settings.mBassRange ,settings.mSurroundDepth ,settings.mSurroundDelay ,settings.mLoopCount ); } #endif static struct modplug_data *make_modplug_data(const char *file) { struct modplug_data *data; data = (struct modplug_data *)xmalloc (sizeof(struct modplug_data)); data->modplugfile = NULL; data->filedata = NULL; decoder_error_init (&data->error); struct io_stream *s = io_open(file, 0); if(!io_ok(s)) { decoder_error(&data->error, ERROR_FATAL, 0, "Can't open file: %s", file); io_close(s); return data; } off_t size = io_file_size(s); if (!RANGE(1, size, INT_MAX)) { decoder_error(&data->error, ERROR_FATAL, 0, "Module size unsuitable for loading: %s", file); io_close(s); return data; } // if(size>MAXMODSIZE) { // io_close(s); // decoder_error(&data->error, ERROR_FATAL, 0, "Module to big! 42M ain't enough ? (%s)", file); // return data; // } data->filedata = (char *)xmalloc((size_t)size); io_read(s, data->filedata, (size_t)size); io_close(s); data->modplugfile=ModPlug_Load(data->filedata, (int)size); if(data->modplugfile==NULL) { free(data->filedata); decoder_error(&data->error, ERROR_FATAL, 0, "Can't load module: %s", file); return data; } return data; } static void *modplug_open (const char *file) { // this is not really needed but without it the calls would still be made // and thus time gets wasted... #if !defined(NDEBUG) && defined(DEBUG) if(doDebugSettings) { doDebugSettings=0; debugSettings(); } #endif struct modplug_data *data = make_modplug_data(file); if(data->modplugfile) { data->length = ModPlug_GetLength(data->modplugfile); } if(data->modplugfile) { debug ("Opened file %s", file); } return data; } static void modplug_close (void *void_data) { struct modplug_data *data = (struct modplug_data *)void_data; if (data->modplugfile) { ModPlug_Unload(data->modplugfile); free(data->filedata); } decoder_error_clear (&data->error); free (data); } static void modplug_info (const char *file_name, struct file_tags *info, const int tags_sel) { struct modplug_data *data = make_modplug_data(file_name); if(data->modplugfile==NULL) return; if(tags_sel & TAGS_TIME) { info->time = ModPlug_GetLength(data->modplugfile) / 1000; info->filled |= TAGS_TIME; } if(tags_sel & TAGS_COMMENTS) { info->title = xstrdup(ModPlug_GetName(data->modplugfile)); info->filled |= TAGS_COMMENTS; } modplug_close(data); } static int modplug_seek (void *void_data, int sec) { struct modplug_data *data = (struct modplug_data *)void_data; assert (sec >= 0); int ms = sec*1000; ms = MIN(ms,data->length); ModPlug_Seek(data->modplugfile, ms); return ms/1000; } static int modplug_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct modplug_data *data = (struct modplug_data *)void_data; sound_params->channels = settings.mChannels; sound_params->rate = settings.mFrequency; sound_params->fmt = ((settings.mBits==16)?SFMT_S16:(settings.mBits==8)?SFMT_S8:SFMT_S32) | SFMT_NE; return ModPlug_Read(data->modplugfile, buf, buf_len); } static int modplug_get_bitrate (void *unused ATTR_UNUSED) { return -1; } static int modplug_get_duration (void *void_data) { struct modplug_data *data = (struct modplug_data *)void_data; return data->length/1000; } static int modplug_our_format_ext(const char *ext) { // Do not include non-module formats in this list (even if // ModPlug supports them). Doing so may cause memory exhaustion // in make_modplug_data(). return !strcasecmp (ext, "NONE") || !strcasecmp (ext, "MOD") || !strcasecmp (ext, "S3M") || !strcasecmp (ext, "XM") || !strcasecmp (ext, "MED") || !strcasecmp (ext, "MTM") || !strcasecmp (ext, "IT") || !strcasecmp (ext, "669") || !strcasecmp (ext, "ULT") || !strcasecmp (ext, "STM") || !strcasecmp (ext, "FAR") || !strcasecmp (ext, "AMF") || !strcasecmp (ext, "AMS") || !strcasecmp (ext, "DSM") || !strcasecmp (ext, "MDL") || !strcasecmp (ext, "OKT") || // modplug can do MIDI but not in this form... //!strcasecmp (ext, "MID") || !strcasecmp (ext, "DMF") || !strcasecmp (ext, "PTM") || !strcasecmp (ext, "DBM") || !strcasecmp (ext, "MT2") || !strcasecmp (ext, "AMF0") || !strcasecmp (ext, "PSM") || !strcasecmp (ext, "J2B") || !strcasecmp (ext, "UMX"); } static void modplug_get_error (void *prv_data, struct decoder_error *error) { struct modplug_data *data = (struct modplug_data *)prv_data; decoder_error_copy (error, &data->error); } static struct decoder modplug_decoder = { DECODER_API_VERSION, NULL, NULL, modplug_open, NULL, NULL, modplug_close, modplug_decode, modplug_seek, modplug_info, modplug_get_bitrate, modplug_get_duration, modplug_get_error, modplug_our_format_ext, NULL, NULL, NULL, NULL, NULL }; struct decoder *plugin_init () { ModPlug_GetSettings(&settings); settings.mFlags = 0; settings.mFlags |= options_get_bool("ModPlug_Oversampling") ?MODPLUG_ENABLE_OVERSAMPLING:0; settings.mFlags |= options_get_bool("ModPlug_NoiseReduction") ?MODPLUG_ENABLE_NOISE_REDUCTION:0; settings.mFlags |= options_get_bool("ModPlug_Reverb") ?MODPLUG_ENABLE_REVERB:0; settings.mFlags |= options_get_bool("ModPlug_MegaBass") ?MODPLUG_ENABLE_MEGABASS:0; settings.mFlags |= options_get_bool("ModPlug_Surround") ?MODPLUG_ENABLE_SURROUND:0; if(!strcasecmp(options_get_symb("ModPlug_ResamplingMode"), "FIR")) settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; if(!strcasecmp(options_get_symb("ModPlug_ResamplingMode"), "SPLINE")) settings.mResamplingMode = MODPLUG_RESAMPLE_SPLINE; if(!strcasecmp(options_get_symb("ModPlug_ResamplingMode"), "LINEAR")) settings.mResamplingMode = MODPLUG_RESAMPLE_LINEAR; if(!strcasecmp(options_get_symb("ModPlug_ResamplingMode"), "NEAREST")) settings.mResamplingMode = MODPLUG_RESAMPLE_NEAREST; settings.mChannels = options_get_int("ModPlug_Channels"); settings.mBits = options_get_int("ModPlug_Bits"); settings.mFrequency = options_get_int("ModPlug_Frequency"); settings.mReverbDepth = options_get_int("ModPlug_ReverbDepth"); settings.mReverbDelay = options_get_int("ModPlug_ReverbDelay"); settings.mBassAmount = options_get_int("ModPlug_BassAmount"); settings.mBassRange = options_get_int("ModPlug_BassRange"); settings.mSurroundDepth = options_get_int("ModPlug_SurroundDepth"); settings.mSurroundDelay = options_get_int("ModPlug_SurroundDelay"); settings.mLoopCount = options_get_int("ModPlug_LoopCount"); ModPlug_SetSettings(&settings); return &modplug_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/modplug/modplug.m40000664000076400000500000000075211611375037021665 0ustar riesebiesrcdnl libmodplug AC_ARG_WITH(modplug, AS_HELP_STRING([--without-modplug], [Compile without libmodplug])) if test "x$with_modplug" != "xno" then PKG_CHECK_MODULES(modplug, libmodplug >= 0.7, [AC_SUBST(modplug_LIBS) AC_SUBST(modplug_CFLAGS) want_modplug="yes" DECODER_PLUGINS="$DECODER_PLUGINS modplug"], [true]) fi AM_CONDITIONAL([BUILD_modplug], [test "$want_modplug"]) AC_CONFIG_FILES([decoder_plugins/modplug/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/mp3/0002775000076400000500000000000013710016723016776 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/mp3/Makefile.am0000664000076400000500000000042210360307734021031 0ustar riesebiesrclib_LTLIBRARIES = libmp3_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libmp3_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libmp3_decoder_la_CFLAGS = -I$(top_srcdir) libmp3_decoder_la_LIBADD = -lmad -lid3tag -lz $(RCC_LIBS) libmp3_decoder_la_SOURCES = mp3.c xing.c xing.h moc-2.6.0~svn-r3005/decoder_plugins/mp3/mp3.c0000664000076400000500000004351712661770467017671 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2002 - 2006 Damian Pietras * * 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 code was based on madlld.c (C) by Bertrand Petit including code * from xmms-mad (C) by Sam Clegg and winamp plugin for madlib (C) by * Robert Leslie. */ /* FIXME: there can be a bit of silence in mp3 at the end or at the * beginning. If you hear gaps between files, it's the file's fault. * Can we strip this silence? */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_ICONV # include #endif #define DEBUG #include "common.h" #include "log.h" #include "xing.h" #include "audio.h" #include "decoder.h" #include "io.h" #include "options.h" #include "files.h" #include "utf8.h" #include "rcc.h" #define INPUT_BUFFER (32 * 1024) static iconv_t iconv_id3_fix; struct mp3_data { struct io_stream *io_stream; unsigned long bitrate; long avg_bitrate; unsigned int freq; short channels; signed long duration; /* Total time of the file in seconds (used for seeking). */ off_t size; /* Size of the file */ unsigned char in_buff[INPUT_BUFFER + MAD_BUFFER_GUARD]; struct mad_stream stream; struct mad_frame frame; struct mad_synth synth; int skip_frames; /* how many frames to skip (after seeking) */ int ok; /* was this stream successfully opened? */ struct decoder_error error; }; /* Fill in the mad buffer, return number of bytes read, 0 on eof or error */ static size_t fill_buff (struct mp3_data *data) { size_t remaining; ssize_t read_size; unsigned char *read_start; if (data->stream.next_frame != NULL) { remaining = data->stream.bufend - data->stream.next_frame; memmove (data->in_buff, data->stream.next_frame, remaining); read_start = data->in_buff + remaining; read_size = INPUT_BUFFER - remaining; } else { read_start = data->in_buff; read_size = INPUT_BUFFER; remaining = 0; } read_size = io_read (data->io_stream, read_start, read_size); if (read_size < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "read error: %s", io_strerror(data->io_stream)); return 0; } else if (read_size == 0) return 0; if (io_eof (data->io_stream)) { memset (read_start + read_size, 0, MAD_BUFFER_GUARD); read_size += MAD_BUFFER_GUARD; } mad_stream_buffer(&data->stream, data->in_buff, read_size + remaining); data->stream.error = 0; return read_size; } static char *id3v1_fix (const char *str) { if (iconv_id3_fix != (iconv_t)-1) return iconv_str (iconv_id3_fix, str); return xstrdup (str); } int __unique_frame (struct id3_tag *tag, struct id3_frame *frame) { unsigned int i; for (i = 0; i < tag->nframes; i++) { if (tag->frames[i] == frame) { break; } } for (; i < tag->nframes; i++) { if (strcmp(tag->frames[i]->id, frame->id) == 0) { return 0; } } return 1; } static char *get_tag (struct id3_tag *tag, const char *what) { struct id3_frame *frame; union id3_field *field; const id3_ucs4_t *ucs4; char *comm = NULL; frame = id3_tag_findframe (tag, what, 0); if (frame && (field = &frame->fields[1])) { ucs4 = id3_field_getstrings (field, 0); if (ucs4) { /* Workaround for ID3 tags v1/v1.1 where the encoding * is latin1. */ union id3_field *encoding_field = &frame->fields[0]; if (((id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) && __unique_frame(tag, frame)) || ((options_get_bool ("EnforceTagsEncoding") && (id3_field_gettextencoding((encoding_field)) == ID3_FIELD_TEXTENCODING_ISO_8859_1)))) { char *t; comm = (char *)id3_ucs4_latin1duplicate (ucs4); #ifdef HAVE_RCC if (options_get_bool("UseRCC")) comm = rcc_reencode (comm); else { #endif /* HAVE_RCC */ t = comm; comm = id3v1_fix (comm); free (t); #ifdef HAVE_RCC } #endif /* HAVE_RCC */ } else comm = (char *)id3_ucs4_utf8duplicate (ucs4); } } return comm; } static int count_time_internal (struct mp3_data *data) { struct xing xing; unsigned long bitrate = 0; int has_xing = 0; int is_vbr = 0; int num_frames = 0; mad_timer_t duration = mad_timer_zero; struct mad_header header; int good_header = 0; /* Have we decoded any header? */ mad_header_init (&header); xing_init (&xing); /* There are three ways of calculating the length of an mp3: 1) Constant bitrate: One frame can provide the information needed: # of frames and duration. Just see how long it is and do the division. 2) Variable bitrate: Xing tag. It provides the number of frames. Each frame has the same number of samples, so just use that. 3) All: Count up the frames and duration of each frame by decoding each one. We do this if we've no other choice, i.e. if it's a VBR file with no Xing tag. */ while (1) { /* Fill the input buffer if needed */ if (data->stream.buffer == NULL || data->stream.error == MAD_ERROR_BUFLEN) { if (!fill_buff(data)) break; } if (mad_header_decode(&header, &data->stream) == -1) { if (MAD_RECOVERABLE(data->stream.error)) continue; else if (data->stream.error == MAD_ERROR_BUFLEN) continue; else { debug ("Can't decode header: %s", mad_stream_errorstr(&data->stream)); break; } } good_header = 1; /* Limit xing testing to the first frame header */ if (!num_frames++) { if (xing_parse(&xing, data->stream.anc_ptr, data->stream.anc_bitlen) != -1) { is_vbr = 1; debug ("Has XING header"); if (xing.flags & XING_FRAMES) { has_xing = 1; num_frames = xing.frames; break; } debug ("XING header doesn't contain number of frames."); } } /* Test the first n frames to see if this is a VBR file */ if (!is_vbr && !(num_frames > 20)) { if (bitrate && header.bitrate != bitrate) { debug ("Detected VBR after %d frames", num_frames); is_vbr = 1; } else bitrate = header.bitrate; } /* We have to assume it's not a VBR file if it hasn't already * been marked as one and we've checked n frames for different * bitrates */ else if (!is_vbr) { debug ("Fixed rate MP3"); break; } mad_timer_add (&duration, header.duration); } if (!good_header) return -1; if (data->size == -1) { mad_header_finish(&header); return -1; } if (!is_vbr) { /* time in seconds */ double time = (data->size * 8.0) / (header.bitrate); double timefrac = (double)time - ((long)(time)); /* samples per frame */ long nsamples = 32 * MAD_NSBSAMPLES(&header); /* samplerate is a constant */ num_frames = (long) (time * header.samplerate / nsamples); /* the average bitrate is the constant bitrate */ data->avg_bitrate = bitrate; mad_timer_set(&duration, (long)time, (long)(timefrac*100), 100); } else if (has_xing) { mad_timer_multiply (&header.duration, num_frames); duration = header.duration; } else { /* the durations have been added up, and the number of frames counted. We do nothing here. */ debug ("Counted duration by counting frames durations in VBR file."); } if (data->avg_bitrate == -1 && mad_timer_count(duration, MAD_UNITS_SECONDS) > 0) { data->avg_bitrate = data->size / mad_timer_count(duration, MAD_UNITS_SECONDS) * 8; } mad_header_finish(&header); debug ("MP3 time: %ld", mad_timer_count (duration, MAD_UNITS_SECONDS)); return mad_timer_count (duration, MAD_UNITS_SECONDS); } static struct mp3_data *mp3_open_internal (const char *file, const int buffered) { struct mp3_data *data; data = (struct mp3_data *)xmalloc (sizeof(struct mp3_data)); data->ok = 0; decoder_error_init (&data->error); /* Reset information about the file */ data->freq = 0; data->channels = 0; data->skip_frames = 0; data->bitrate = -1; data->avg_bitrate = -1; /* Open the file */ data->io_stream = io_open (file, buffered); if (io_ok(data->io_stream)) { data->ok = 1; data->size = io_file_size (data->io_stream); mad_stream_init (&data->stream); mad_frame_init (&data->frame); mad_synth_init (&data->synth); if (options_get_bool ("MP3IgnoreCRCErrors")) mad_stream_options (&data->stream, MAD_OPTION_IGNORECRC); data->duration = count_time_internal (data); mad_frame_mute (&data->frame); data->stream.next_frame = NULL; data->stream.sync = 0; data->stream.error = MAD_ERROR_NONE; if (io_seek(data->io_stream, 0, SEEK_SET) == -1) { decoder_error (&data->error, ERROR_FATAL, 0, "seek failed"); mad_stream_finish (&data->stream); mad_frame_finish (&data->frame); mad_synth_finish (&data->synth); data->ok = 0; } data->stream.error = MAD_ERROR_BUFLEN; } else { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open: %s", io_strerror(data->io_stream)); } return data; } static void *mp3_open (const char *file) { return mp3_open_internal (file, 1); } static void *mp3_open_stream (struct io_stream *stream) { struct mp3_data *data; data = (struct mp3_data *)xmalloc (sizeof(struct mp3_data)); data->ok = 1; decoder_error_init (&data->error); /* Reset information about the file */ data->freq = 0; data->channels = 0; data->skip_frames = 0; data->bitrate = -1; data->io_stream = stream; data->duration = -1; data->size = -1; mad_stream_init (&data->stream); mad_frame_init (&data->frame); mad_synth_init (&data->synth); if (options_get_bool ("MP3IgnoreCRCErrors")) mad_stream_options (&data->stream, MAD_OPTION_IGNORECRC); return data; } static void mp3_close (void *void_data) { struct mp3_data *data = (struct mp3_data *)void_data; if (data->ok) { mad_stream_finish (&data->stream); mad_frame_finish (&data->frame); mad_synth_finish (&data->synth); } io_close (data->io_stream); decoder_error_clear (&data->error); free (data); } /* Get the time for mp3 file, return -1 on error. * Adapted from mpg321. */ static int count_time (const char *file) { struct mp3_data *data; int time; debug ("Processing file %s", file); data = mp3_open_internal (file, 0); if (!data->ok) time = -1; else time = data->duration; mp3_close (data); return time; } /* Fill info structure with data from the id3 tag */ static void mp3_info (const char *file_name, struct file_tags *info, const int tags_sel) { if (tags_sel & TAGS_COMMENTS) { struct id3_tag *tag; struct id3_file *id3file; char *track = NULL; id3file = id3_file_open (file_name, ID3_FILE_MODE_READONLY); if (!id3file) return; tag = id3_file_tag (id3file); if (tag) { info->artist = get_tag (tag, ID3_FRAME_ARTIST); info->title = get_tag (tag, ID3_FRAME_TITLE); info->album = get_tag (tag, ID3_FRAME_ALBUM); track = get_tag (tag, ID3_FRAME_TRACK); if (track) { char *end; info->track = strtol (track, &end, 10); if (end == track) info->track = -1; free (track); } } id3_file_close (id3file); } if (tags_sel & TAGS_TIME) info->time = count_time (file_name); } static inline int32_t round_sample (mad_fixed_t sample) { sample += 1L << (MAD_F_FRACBITS - 24); sample = CLAMP(-MAD_F_ONE, sample, MAD_F_ONE - 1); return sample >> (MAD_F_FRACBITS + 1 - 24); } static int put_output (char *buf, int buf_len, struct mad_pcm *pcm, struct mad_header *header) { unsigned int nsamples; mad_fixed_t const *left_ch, *right_ch; int olen; nsamples = pcm->length; left_ch = pcm->samples[0]; right_ch = pcm->samples[1]; olen = nsamples * MAD_NCHANNELS (header) * 4; if (olen > buf_len) { logit ("PCM buffer to small!"); return 0; } while (nsamples--) { long sample0 = round_sample (*left_ch++); buf[0] = 0; buf[1] = sample0; buf[2] = sample0 >> 8; buf[3] = sample0 >> 16; buf += 4; if (MAD_NCHANNELS(header) == 2) { long sample1; sample1 = round_sample (*right_ch++); buf[0] = 0; buf[1] = sample1; buf[2] = sample1 >> 8; buf[3] = sample1 >> 16; buf += 4; } } return olen; } /* If the current frame in the stream is an ID3 tag, then swallow it. */ static ssize_t flush_id3_tag (struct mp3_data *data) { size_t remaining; ssize_t tag_size; remaining = data->stream.bufend - data->stream.next_frame; tag_size = id3_tag_query (data->stream.this_frame, remaining); if (tag_size > 0) { mad_stream_skip (&data->stream, tag_size); mad_stream_sync (&data->stream); } return tag_size; } static int mp3_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct mp3_data *data = (struct mp3_data *)void_data; decoder_error_clear (&data->error); while (1) { /* Fill the input buffer if needed */ if (data->stream.buffer == NULL || data->stream.error == MAD_ERROR_BUFLEN) { if (!fill_buff(data)) return 0; } if (mad_frame_decode (&data->frame, &data->stream)) { if (flush_id3_tag (data)) continue; else if (MAD_RECOVERABLE(data->stream.error)) { /* Ignore LOSTSYNC */ if (data->stream.error == MAD_ERROR_LOSTSYNC) continue; if (!data->skip_frames) decoder_error (&data->error, ERROR_STREAM, 0, "Broken frame: %s", mad_stream_errorstr(&data->stream)); continue; } else if (data->stream.error == MAD_ERROR_BUFLEN) continue; else { decoder_error (&data->error, ERROR_FATAL, 0, "Broken frame: %s", mad_stream_errorstr(&data->stream)); return 0; } } if (data->skip_frames) { data->skip_frames--; continue; } /* Sound parameters. */ if (!(sound_params->rate = data->frame.header.samplerate)) { decoder_error (&data->error, ERROR_FATAL, 0, "Broken file: information about the" " frequency couldn't be read."); return 0; } sound_params->channels = MAD_NCHANNELS(&data->frame.header); sound_params->fmt = SFMT_S32 | SFMT_LE; /* Change of the bitrate? */ if (data->frame.header.bitrate != data->bitrate) { if ((data->bitrate = data->frame.header.bitrate) == 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Broken file: information about the" " bitrate couldn't be read."); return 0; } } mad_synth_frame (&data->synth, &data->frame); mad_stream_sync (&data->stream); return put_output (buf, buf_len, &data->synth.pcm, &data->frame.header); } } static int mp3_seek (void *void_data, int sec) { struct mp3_data *data = (struct mp3_data *)void_data; off_t new_position; assert (sec >= 0); if (data->size == -1) return -1; if (sec >= data->duration) return -1; new_position = ((double) sec / (double) data->duration) * data->size; debug ("Seeking to %d (byte %"PRId64")", sec, new_position); if (new_position < 0) new_position = 0; else if (new_position >= data->size) return -1; if (io_seek(data->io_stream, new_position, SEEK_SET) == -1) { logit ("seek to %"PRId64" failed", new_position); return -1; } data->stream.error = MAD_ERROR_BUFLEN; mad_frame_mute (&data->frame); mad_synth_mute (&data->synth); data->stream.sync = 0; data->stream.next_frame = NULL; data->skip_frames = 2; return sec; } static int mp3_get_bitrate (void *void_data) { struct mp3_data *data = (struct mp3_data *)void_data; return data->bitrate / 1000; } static int mp3_get_avg_bitrate (void *void_data) { struct mp3_data *data = (struct mp3_data *)void_data; return data->avg_bitrate / 1000; } static int mp3_get_duration (void *void_data) { struct mp3_data *data = (struct mp3_data *)void_data; return data->duration; } static void mp3_get_name (const char *file, char buf[4]) { char *ext; strcpy (buf, "MPx"); ext = ext_pos (file); if (ext) { if (!strcasecmp (ext, "mp3")) strcpy (buf, "MP3"); else if (!strcasecmp (ext, "mp2")) strcpy (buf, "MP2"); else if (!strcasecmp (ext, "mp1")) strcpy (buf, "MP1"); else if (!strcasecmp (ext, "mpga")) strcpy (buf, "MPG"); } } static int mp3_our_format_ext (const char *ext) { return !strcasecmp (ext, "mp3") || !strcasecmp (ext, "mpga") || !strcasecmp (ext, "mp2") || !strcasecmp (ext, "mp1"); } static void mp3_get_error (void *prv_data, struct decoder_error *error) { struct mp3_data *data = (struct mp3_data *)prv_data; decoder_error_copy (error, &data->error); } static struct io_stream *mp3_get_stream (void *prv_data) { struct mp3_data *data = (struct mp3_data *)prv_data; return data->io_stream; } static int mp3_our_mime (const char *mime) { return !strcasecmp (mime, "audio/mpeg") || !strncasecmp (mime, "audio/mpeg;", 11); } static int mp3_can_decode (struct io_stream *stream) { unsigned char buf[16 * 1024]; /* We must use such a sophisticated test, because there are Shoutcast * servers that can start broadcasting in the middle of a frame, so we * can't use any fewer bytes for magic values. */ if (io_peek(stream, buf, sizeof(buf)) == sizeof(buf)) { struct mad_stream stream; struct mad_header header; int dec_res; mad_stream_init (&stream); mad_header_init (&header); mad_stream_buffer (&stream, buf, sizeof(buf)); stream.error = 0; while ((dec_res = mad_header_decode(&header, &stream)) == -1 && MAD_RECOVERABLE(stream.error)) ; return dec_res != -1 ? 1 : 0; } return 0; } static void mp3_init () { iconv_id3_fix = iconv_open ("UTF-8", options_get_str("ID3v1TagsEncoding")); if (iconv_id3_fix == (iconv_t)(-1)) log_errno ("iconv_open() failed", errno); } static void mp3_destroy () { if (iconv_close(iconv_id3_fix) == -1) log_errno ("iconv_close() failed", errno); } static struct decoder mp3_decoder = { DECODER_API_VERSION, mp3_init, mp3_destroy, mp3_open, mp3_open_stream, mp3_can_decode, mp3_close, mp3_decode, mp3_seek, mp3_info, mp3_get_bitrate, mp3_get_duration, mp3_get_error, mp3_our_format_ext, mp3_our_mime, mp3_get_name, NULL, mp3_get_stream, mp3_get_avg_bitrate }; struct decoder *plugin_init () { return &mp3_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/mp3/mp3.m40000664000076400000500000000075311776160731017753 0ustar riesebiesrcdnl libmad (mp3) AC_ARG_WITH(mp3, AS_HELP_STRING([--without-mp3], [Compile without mp3 support (libmad)])) if test "x$with_mp3" != "xno" then AC_CHECK_LIB(mad, mad_stream_init, [ AC_CHECK_HEADER([mad.h], ,)]) if test "$ac_cv_lib_mad_mad_stream_init" = "yes" -a "$HAVE_ID3TAG" = "yes" then want_mp3="yes" DECODER_PLUGINS="$DECODER_PLUGINS mp3" fi fi AM_CONDITIONAL([BUILD_mp3], [test "$want_mp3"]) AC_CONFIG_FILES([decoder_plugins/mp3/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/mp3/xing.c0000664000076400000500000000402712202530312020076 0ustar riesebiesrc/* * mad - MPEG audio decoder * Copyright (C) 2000-2001 Robert Leslie * * 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, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id: xing.c,v 1.5 2002/11/18 16:32:21 daper Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "xing.h" #define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g') /* * NAME: xing->init() * DESCRIPTION: initialize Xing structure */ void xing_init(struct xing *xing) { xing->flags = 0; } /* * NAME: xing->parse() * DESCRIPTION: parse a Xing VBR header */ int xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen) { if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC) goto fail; xing->flags = mad_bit_read(&ptr, 32); bitlen -= 64; if (xing->flags & XING_FRAMES) { if (bitlen < 32) goto fail; xing->frames = mad_bit_read(&ptr, 32); bitlen -= 32; } if (xing->flags & XING_BYTES) { if (bitlen < 32) goto fail; xing->bytes = mad_bit_read(&ptr, 32); bitlen -= 32; } if (xing->flags & XING_TOC) { int i; if (bitlen < 800) goto fail; for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(&ptr, 8); bitlen -= 800; } if (xing->flags & XING_SCALE) { if (bitlen < 32) goto fail; xing->scale = mad_bit_read(&ptr, 32); bitlen -= 32; } return 0; fail: xing->flags = 0; return -1; } moc-2.6.0~svn-r3005/decoder_plugins/mp3/xing.h0000664000076400000500000000261310204714722020113 0ustar riesebiesrc/* * mad - MPEG audio decoder * Copyright (C) 2000-2001 Robert Leslie * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: xing.h,v 1.3 2002/10/04 13:38:09 daper Exp $ */ #ifndef XING_H #define XING_H #include "mad.h" struct xing { long flags; /* valid fields (see below) */ unsigned long frames; /* total number of frames */ unsigned long bytes; /* total number of bytes */ unsigned char toc[100]; /* 100-point seek table */ long scale; /* ?? */ }; enum { XING_FRAMES = 0x00000001L, XING_BYTES = 0x00000002L, XING_TOC = 0x00000004L, XING_SCALE = 0x00000008L }; void xing_init (struct xing *); #define xing_finish(xing) /* nothing */ int xing_parse (struct xing *, struct mad_bitptr, unsigned int); #endif moc-2.6.0~svn-r3005/decoder_plugins/musepack/0002775000076400000500000000000013710016723020107 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/musepack/Makefile.am0000664000076400000500000000051510253742412022142 0ustar riesebiesrclib_LTLIBRARIES = libmusepack_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libmusepack_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libmusepack_decoder_la_LIBADD = $(MUSEPACK_LIBS) $(TAGLIB_LIBS) libmusepack_decoder_la_CFLAGS = $(MUSEPACK_CFLAGS) $(TAGLIB_CFLAGS) \ -I$(top_srcdir) libmusepack_decoder_la_SOURCES = musepack.c moc-2.6.0~svn-r3005/decoder_plugins/musepack/musepack.c0000664000076400000500000002573012735111065022071 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ /* FIXME: mpc_decoder_decode() can give fixed point values, do we have to * handle this case? */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #ifdef MPC_IS_OLD_API # include #else # include #endif #include #define DEBUG #include "common.h" #include "log.h" #include "decoder.h" #include "io.h" #include "audio.h" struct musepack_data { struct io_stream *stream; #ifdef MPC_IS_OLD_API mpc_decoder decoder; #else mpc_demux *demux; #endif mpc_reader reader; mpc_streaminfo info; int avg_bitrate; int bitrate; struct decoder_error error; int ok; /* was this stream successfully opened? */ float *remain_buf; size_t remain_buf_len; /* in samples (sizeof(float)) */ }; #ifdef MPC_IS_OLD_API static mpc_int32_t read_cb (void *t, void *buf, mpc_int32_t size) #else static mpc_int32_t read_cb (mpc_reader *t, void *buf, mpc_int32_t size) #endif { #ifdef MPC_IS_OLD_API struct musepack_data *data = (struct musepack_data *)t; #else struct musepack_data *data = t->data; #endif ssize_t res; res = io_read (data->stream, buf, size); if (res < 0) { logit ("Read error"); res = 0; } return res; } #ifdef MPC_IS_OLD_API static mpc_bool_t seek_cb (void *t, mpc_int32_t offset) #else static mpc_bool_t seek_cb (mpc_reader *t, mpc_int32_t offset) #endif { #ifdef MPC_IS_OLD_API struct musepack_data *data = (struct musepack_data *)t; #else struct musepack_data *data = t->data; #endif debug ("Seek request to %"PRId32, offset); return io_seek(data->stream, offset, SEEK_SET) >= 0 ? 1 : 0; } #ifdef MPC_IS_OLD_API static mpc_int32_t tell_cb (void *t) #else static mpc_int32_t tell_cb (mpc_reader *t) #endif { #ifdef MPC_IS_OLD_API struct musepack_data *data = (struct musepack_data *)t; #else struct musepack_data *data = t->data; #endif debug ("tell callback"); return (mpc_int32_t)io_tell (data->stream); } #ifdef MPC_IS_OLD_API static mpc_int32_t get_size_cb (void *t) #else static mpc_int32_t get_size_cb (mpc_reader *t) #endif { #ifdef MPC_IS_OLD_API struct musepack_data *data = (struct musepack_data *)t; #else struct musepack_data *data = t->data; #endif debug ("size callback"); return (mpc_int32_t)io_file_size (data->stream); } #ifdef MPC_IS_OLD_API static mpc_bool_t canseek_cb (void *t) #else static mpc_bool_t canseek_cb (mpc_reader *t) #endif { #ifdef MPC_IS_OLD_API struct musepack_data *data = (struct musepack_data *)t; #else struct musepack_data *data = t->data; #endif return io_seekable (data->stream); } static void musepack_open_stream_internal (struct musepack_data *data) { data->reader.read = read_cb; data->reader.seek = seek_cb; data->reader.tell = tell_cb; data->reader.get_size = get_size_cb; data->reader.canseek = canseek_cb; data->reader.data = data; #ifdef MPC_IS_OLD_API mpc_streaminfo_init (&data->info); if (mpc_streaminfo_read(&data->info, &data->reader) != ERROR_CODE_OK) { decoder_error (&data->error, ERROR_FATAL, 0, "Not a valid MPC file."); return; } mpc_decoder_setup (&data->decoder, &data->reader); if (!mpc_decoder_initialize(&data->decoder, &data->info)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't initialize mpc decoder."); return; } #else data->demux = mpc_demux_init (&data->reader); if (!data->demux) { decoder_error (&data->error, ERROR_FATAL, 0, "Not a valid MPC file."); return; } mpc_demux_get_info (data->demux, &data->info); #endif data->avg_bitrate = (int) (data->info.average_bitrate / 1000); debug ("Avg bitrate: %d", data->avg_bitrate); data->remain_buf = NULL; data->remain_buf_len = 0; data->bitrate = 0; data->ok = 1; } static void *musepack_open (const char *file) { struct musepack_data *data; data = (struct musepack_data *)xmalloc (sizeof(struct musepack_data)); data->ok = 0; decoder_error_init (&data->error); data->stream = io_open (file, 1); if (!io_ok(data->stream)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open file: %s", io_strerror(data->stream)); return data; } /* This a restriction placed on us by the Musepack API. */ if (io_file_size (data->stream) > INT32_MAX) { decoder_error (&data->error, ERROR_FATAL, 0, "File too large!"); return data; } musepack_open_stream_internal (data); return data; } static void *musepack_open_stream (struct io_stream *stream) { struct musepack_data *data; data = (struct musepack_data *)xmalloc (sizeof(struct musepack_data)); data->ok = 0; decoder_error_init (&data->error); data->stream = stream; musepack_open_stream_internal (data); return data; } static void musepack_close (void *prv_data) { struct musepack_data *data = (struct musepack_data *)prv_data; if (data->ok) { #ifndef MPC_IS_OLD_API mpc_demux_exit (data->demux); #endif if (data->remain_buf) free (data->remain_buf); } io_close (data->stream); decoder_error_clear (&data->error); free (data); } static char *tag_str (const char *str) { return str && str[0] ? xstrdup(str) : NULL; } /* Fill info structure with data from musepack comments */ static void musepack_info (const char *file_name, struct file_tags *info, const int tags_sel) { if (tags_sel & TAGS_COMMENTS) { TagLib_File *tf; tf = taglib_file_new_type (file_name, TagLib_File_MPC); if (tf) { TagLib_Tag *tt; tt = taglib_file_tag (tf); if (tt) { info->title = tag_str (taglib_tag_title(tt)); info->artist = tag_str (taglib_tag_artist(tt)); info->album = tag_str (taglib_tag_album(tt)); info->track = taglib_tag_track(tt); if (info->track == 0) info->track = -1; } taglib_file_free (tf); taglib_tag_free_strings (); } else logit ("taglib_file_new_type() failed."); } if (tags_sel & TAGS_TIME) { struct musepack_data *data = musepack_open (file_name); if (data->error.type == ERROR_OK) info->time = mpc_streaminfo_get_length (&data->info); musepack_close (data); } } static int musepack_seek (void *prv_data, int sec) { struct musepack_data *data = (struct musepack_data *)prv_data; int res; assert (sec >= 0); #ifdef MPC_IS_OLD_API res = mpc_decoder_seek_seconds (&data->decoder, sec) ? sec : -1; #else mpc_status status; status = mpc_demux_seek_second (data->demux, sec); if (status == MPC_STATUS_OK) res = sec; else res = -1; #endif if (res != -1 && data->remain_buf) { free (data->remain_buf); data->remain_buf = NULL; data->remain_buf_len = 0; } return res; } static int musepack_decode (void *prv_data, char *buf, int buf_len, struct sound_params *sound_params) { struct musepack_data *data = (struct musepack_data *)prv_data; int decoded; int bytes_from_decoder; #ifndef MPC_IS_OLD_API mpc_frame_info frame; mpc_status err; #else int ret; mpc_uint32_t vbrAcc = 0; mpc_uint32_t vbrUpd = 0; #endif float decode_buf[MPC_DECODER_BUFFER_LENGTH]; if (data->remain_buf) { size_t to_copy = MIN((unsigned int)buf_len, data->remain_buf_len * sizeof(float)); debug ("Copying %zu bytes from the remain buf", to_copy); memcpy (buf, data->remain_buf, to_copy); if (to_copy / sizeof(float) < data->remain_buf_len) { memmove (data->remain_buf, data->remain_buf + to_copy, data->remain_buf_len * sizeof(float) - to_copy); data->remain_buf_len -= to_copy / sizeof(float); } else { debug ("Remain buf is now empty"); free (data->remain_buf); data->remain_buf = NULL; data->remain_buf_len = 0; } return to_copy; } #ifdef MPC_IS_OLD_API ret = mpc_decoder_decode (&data->decoder, decode_buf, &vbrAcc, &vbrUpd); if (ret == 0) { debug ("EOF"); return 0; } if (ret < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Error in the stream!"); return 0; } bytes_from_decoder = ret * sizeof(float) * 2; /* stereo */ data->bitrate = vbrUpd * sound_params->rate / 1152 / 1000; #else do { frame.buffer = decode_buf; err = mpc_demux_decode (data->demux, &frame); if (err == MPC_STATUS_OK && frame.bits == -1) { debug ("EOF"); return 0; } if (err == MPC_STATUS_OK) continue; if (frame.bits == -1) { decoder_error (&data->error, ERROR_FATAL, 0, "Error in the stream!"); return 0; } decoder_error (&data->error, ERROR_STREAM, 0, "Broken frame."); } while (err != MPC_STATUS_OK || frame.samples == 0); mpc_demux_get_info (data->demux, &data->info); bytes_from_decoder = frame.samples * sizeof(MPC_SAMPLE_FORMAT) * data->info.channels; data->bitrate = data->info.bitrate; #endif decoder_error_clear (&data->error); sound_params->channels = data->info.channels; sound_params->rate = data->info.sample_freq; sound_params->fmt = SFMT_FLOAT; if (bytes_from_decoder >= buf_len) { size_t to_copy = MIN (buf_len, bytes_from_decoder); debug ("Copying %zu bytes", to_copy); memcpy (buf, decode_buf, to_copy); data->remain_buf_len = (bytes_from_decoder - to_copy) / sizeof(float); data->remain_buf = (float *)xmalloc (data->remain_buf_len * sizeof(float)); memcpy (data->remain_buf, decode_buf + to_copy, data->remain_buf_len * sizeof(float)); decoded = to_copy; } else { debug ("Copying whole decoded sound (%d bytes)", bytes_from_decoder); memcpy (buf, decode_buf, bytes_from_decoder); decoded = bytes_from_decoder; } return decoded; } static int musepack_get_bitrate (void *prv_data) { struct musepack_data *data = (struct musepack_data *)prv_data; return data->bitrate; } static int musepack_get_avg_bitrate (void *prv_data) { struct musepack_data *data = (struct musepack_data *)prv_data; return data->avg_bitrate; } static int musepack_get_duration (void *prv_data) { struct musepack_data *data = (struct musepack_data *)prv_data; return mpc_streaminfo_get_length (&data->info); } static struct io_stream *musepack_get_stream (void *prv_data) { struct musepack_data *data = (struct musepack_data *)prv_data; return data->stream; } static void musepack_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "MPC"); } static int musepack_our_format_ext (const char *ext) { return !strcasecmp (ext, "mpc"); } static void musepack_get_error (void *prv_data, struct decoder_error *error) { struct musepack_data *data = (struct musepack_data *)prv_data; decoder_error_copy (error, &data->error); } static struct decoder musepack_decoder = { DECODER_API_VERSION, NULL, NULL, musepack_open, musepack_open_stream, NULL /* musepack_can_decode */, musepack_close, musepack_decode, musepack_seek, musepack_info, musepack_get_bitrate, musepack_get_duration, musepack_get_error, musepack_our_format_ext, NULL /*musepack_our_mime*/, musepack_get_name, NULL /* musepack_current_tags */, musepack_get_stream, musepack_get_avg_bitrate }; struct decoder *plugin_init () { return &musepack_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/musepack/musepack.m40000664000076400000500000000334312735111073022162 0ustar riesebiesrcdnl libmpcdec AC_ARG_WITH(musepack, AS_HELP_STRING([--without-musepack], [Compile without musepack (mpc) support])) if test "x$with_musepack" != "xno" then dnl taken from gstreamer AC_CHECK_HEADER([mpc/mpcdec.h], [have_musepack="yes"], [AC_CHECK_HEADER([mpcdec/mpcdec.h], [have_musepack="yes" UPGRADE_MUSEPACK="yes" AC_DEFINE(MPC_IS_OLD_API, 1, [Define if the old MusePack API is used])], [have_musepack="no"])]) if test "x$have_musepack" = "xyes" then MUSEPACK_LIBS="-lmpcdec" AC_SUBST([MUSEPACK_LIBS]) dnl taglib AC_CHECK_PROG([TAGLIB_CONFIG], [taglib-config], [yes]) if test "x$TAGLIB_CONFIG" = "xyes" then AC_MSG_CHECKING([taglib version]) taglib_ver=`taglib-config --version` AX_COMPARE_VERSION($taglib_ver, [ge], [1.3.1]) if test "x$ax_compare_version" = "xtrue" then AC_MSG_RESULT([$taglib_ver, OK]) TAGLIB_CFLAGS="`taglib-config --cflags`" dnl TAGLIB_LIBS="`taglib-config --libs`" TAGLIB_LIBS="-ltag_c" AC_SUBST([TAGLIB_CFLAGS]) AC_SUBST([TAGLIB_LIBS]) dnl check for tag_c.h old_cflags="$CFLAGS" old_cppflags="$CPPFLAGS" CFLAGS="$CFLAGS $TAGLIB_CFLAGS" CPPFLAGS="$CPPFLAGS $TAGLIB_CFLAGS" AC_CHECK_HEADER([tag_c.h], [ want_musepack="yes" DECODER_PLUGINS="$DECODER_PLUGINS musepack" ]) CFLAGS="$old_cflags" CPPFLAGS="$old_cppflags" AX_COMPARE_VERSION($taglib_ver, [lt], [1.5]) if test "x$ax_compare_version" = "xtrue" then UPGRADE_TAGLIB="yes" fi else AC_MSG_RESULT([$taglib_ver, but minimum is 1.3.1 - required for musepack]) fi fi fi fi AM_CONDITIONAL([BUILD_musepack], [test "$want_musepack"]) AC_CONFIG_FILES([decoder_plugins/musepack/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/sidplay2/0002775000076400000500000000000013710016723020026 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/sidplay2/Makefile.am0000664000076400000500000000070311054475420022062 0ustar riesebiesrclib_LTLIBRARIES = libsidplay2_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libsidplay2_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ $(sidplay2_LDFLAGS) libsidplay2_decoder_la_LIBADD = $(sidplay2_LIBS) $(sidutils_LIBS) libsidplay2_decoder_la_CFLAGS = $(sidplay2_CFLAGS) $(sidutils_CFLAGS) -I$(top_srcdir) libsidplay2_decoder_la_CXXFLAGS = $(sidplay2_CFLAGS) $(sidutils_CFLAGS) -I$(top_srcdir) libsidplay2_decoder_la_SOURCES = sidplay2.cc sidplay2.h moc-2.6.0~svn-r3005/decoder_plugins/sidplay2/sidplay2.cc0000664000076400000500000002674413123411535022075 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * libsidplay2-plugin Copyright (C) 2007 Hendrik Iben * Enables MOC to play sids via libsidplay2/libsidutils. * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "common.h" #include "sidplay2.h" #include "log.h" #include "options.h" static SID_EXTERN::sidplay2 *players [POOL_SIZE]; static ReSIDBuilder *builders [POOL_SIZE]; static int playerIndex; static SID_EXTERN::SidDatabase *database; static int init_db; static pthread_mutex_t db_mtx, player_select_mtx; static int defaultLength; static int minLength; static bool startAtStart; static bool playSubTunes; static sidplay2_data * make_data() { pthread_mutex_lock(&player_select_mtx); playerIndex = (playerIndex+1)%POOL_SIZE; struct sidplay2_data *s2d = (struct sidplay2_data *)xmalloc(sizeof(sidplay2_data)); if(players[playerIndex]==NULL) players[playerIndex] = new SID_EXTERN::sidplay2(); s2d->player = players[playerIndex]; s2d->cfg = s2d->player->config(); s2d->cfg.frequency = options_get_int(OPT_FREQ); s2d->cfg.precision = options_get_int(OPT_PREC); s2d->cfg.optimisation = options_get_int(OPT_OPTI); switch(options_get_symb(OPT_PMODE)[0]) { case 'M': s2d->cfg.playback = sid2_mono; break; case 'S': s2d->cfg.playback = sid2_stereo; break; case 'L': s2d->cfg.playback = sid2_left; break; case 'R': s2d->cfg.playback = sid2_right; break; default : s2d->cfg.playback = sid2_mono; } s2d->player->config(s2d->cfg); s2d->cfg = s2d->player->config(); if(builders[playerIndex]==NULL) builders[playerIndex] = new ReSIDBuilder( RESID_ID ); s2d->builder = builders[playerIndex]; pthread_mutex_unlock(&player_select_mtx); if(!(*s2d->builder)) fatal("sidplay2: Cannot create ReSID-Builder!"); s2d->builder->create(s2d->player->info().maxsids); s2d->builder->sampling(s2d->cfg.frequency); s2d->cfg.sidEmulation = s2d->builder; s2d->player->config(s2d->cfg); s2d->cfg = s2d->player->config(); s2d->channels = s2d->player->info().channels; s2d->frequency = s2d->cfg.frequency; #ifdef WORDS_BIGENDIAN s2d->cfg.sampleFormat = SID2_BIG_SIGNED; #else s2d->cfg.sampleFormat = SID2_LITTLE_SIGNED; #endif s2d->player->config(s2d->cfg); s2d->cfg = s2d->player->config(); switch(s2d->cfg.sampleFormat) { case SID2_LITTLE_SIGNED: switch(s2d->cfg.precision) { case 8: s2d->sample_format = SFMT_S8 | SFMT_LE; break; case 16: s2d->sample_format = SFMT_S16 | SFMT_LE; break; case 32: s2d->sample_format = SFMT_S32 | SFMT_LE; break; default: fatal("sidplay2: Unsupported precision: %i", s2d->cfg.precision); } break; case SID2_LITTLE_UNSIGNED: switch(s2d->cfg.precision) { case 8: s2d->sample_format = SFMT_U8 | SFMT_LE; break; case 16: s2d->sample_format = SFMT_U16 | SFMT_LE; break; case 32: s2d->sample_format = SFMT_U32 | SFMT_LE; break; default: fatal("sidplay2: Unsupported precision: %i", s2d->cfg.precision); } break; case SID2_BIG_SIGNED: switch(s2d->cfg.precision) { case 8: s2d->sample_format = SFMT_S8 | SFMT_BE; break; case 16: s2d->sample_format = SFMT_S16 | SFMT_BE; break; case 32: s2d->sample_format = SFMT_S32 | SFMT_BE; break; default: fatal("sidplay2: Unsupported precision: %i", s2d->cfg.precision); } break; case SID2_BIG_UNSIGNED: switch(s2d->cfg.precision) { case 8: s2d->sample_format = SFMT_U8 | SFMT_BE; break; case 16: s2d->sample_format = SFMT_U16 | SFMT_BE; break; case 32: s2d->sample_format = SFMT_U32 | SFMT_BE; break; default: fatal("sidplay2: Unsupported precision: %i", s2d->cfg.precision); } break; default: fatal("sidplay2: Unknown Audio-Format!"); } return s2d; } static void init_database() { int cancel = 0; pthread_mutex_lock(&db_mtx); if(init_db==0) cancel = 1; init_db = 0; pthread_mutex_unlock(&db_mtx); if(cancel) return; char * dbfile = options_get_str(OPT_DATABASE); if(dbfile!=NULL && dbfile[0]!='\0') { database = new SidDatabase(); if(database->open(dbfile)<0) { logit("Unable to open SidDatabase %s", dbfile); database = NULL; } } } extern "C" void *sidplay2_open(const char *file) { if(init_db) init_database(); struct sidplay2_data *s2d = make_data(); decoder_error_init(&s2d->error); s2d->tune=NULL; s2d->sublengths = NULL; s2d->length = 0; SidTuneMod *st = new SidTuneMod(file); if(!(*st)) { decoder_error(&s2d->error, ERROR_FATAL, 0, "Unable to open %s...", file); delete st; return s2d; } s2d->songs = st->getInfo().songs; s2d->sublengths = new int [s2d->songs]; s2d->startSong = st->getInfo().startSong; s2d->timeStart = 1; s2d->timeEnd = s2d->songs; if(startAtStart) s2d->timeStart = s2d->startSong; if(!playSubTunes) s2d->timeEnd = s2d->timeStart; for(int s=s2d->timeStart; s <= s2d->timeEnd; s++) { st->selectSong(s); if(!(*st)) { decoder_error(&s2d->error, ERROR_FATAL, 0, "Error determining length of %s", file); delete st; return s2d; } if(database!=NULL) { int dl = database->length(*st); if(dl<1) dl = defaultLength; if(dllength += dl; s2d->sublengths[s-1] = dl; } else { s2d->length += defaultLength; s2d->sublengths[s-1] = defaultLength; } } // this should not happen normally... if(s2d->length==0) s2d->length = defaultLength; s2d->currentSong = s2d->timeStart; st->selectSong(s2d->currentSong); if(!(*st)) { decoder_error(&s2d->error, ERROR_FATAL, 0, "Cannot select first song in %s", file); delete st; return s2d; } s2d->tune = st; s2d->player->load(st); if(!(*s2d->player)) { decoder_error(&s2d->error, ERROR_FATAL, 0, "%s", s2d->player->error()); } s2d->player->fastForward(100); return ((void *)s2d); } extern "C" void sidplay2_close(void *void_data) { struct sidplay2_data *data = (struct sidplay2_data *)void_data; if(data->player!=NULL) data->player->load(NULL); if(data->tune!=NULL) delete data->tune; if(data->sublengths!=NULL) delete data->sublengths; decoder_error_clear (&data->error); free(data); } extern "C" void sidplay2_get_error (void *prv_data, struct decoder_error *error) { struct sidplay2_data *data = (struct sidplay2_data *)prv_data; decoder_error_copy (error, &data->error); } extern "C" void sidplay2_info (const char *file_name, struct file_tags *info, const int) { if(init_db) init_database(); SidTuneMod *st = new SidTuneMod(file_name); if(!(*st)) { delete st; return; } const SidTuneInfo sti = st->getInfo(); if ( sti.numberOfInfoStrings>0 && sti.infoString[STITLE]!=NULL && strlen(sti.infoString[STITLE])>0 ) { info->title = trim(sti.infoString[STITLE],strlen(sti.infoString[STITLE])); if (info->title) info->filled |= TAGS_COMMENTS; } if ( sti.numberOfInfoStrings>1 && sti.infoString[SAUTHOR]!=NULL && strlen(sti.infoString[SAUTHOR])>0 ) { info->artist = trim(sti.infoString[SAUTHOR],strlen(sti.infoString[SAUTHOR])); if (info->artist) info->filled |= TAGS_COMMENTS; } // Not really album - but close... if ( sti.numberOfInfoStrings>2 && sti.infoString[SCOPY]!=NULL && strlen(sti.infoString[SCOPY])>0 ) { info->album = trim(sti.infoString[SCOPY],strlen(sti.infoString[SCOPY])); if (info->album) info->filled |= TAGS_COMMENTS; } info->time = 0; int songs = st->getInfo().songs; int countStart = 1; int countEnd = songs; if(startAtStart) countStart = st->getInfo().startSong; if(!playSubTunes) countEnd = countStart; for(int s=countStart; s <= countEnd; s++) { st->selectSong(s); if(database!=NULL) { int dl = database->length(*st); if(dl<1) dl = defaultLength; if(dltime += dl; } else { info->time += defaultLength; } } info->filled |= TAGS_TIME; delete st; } /* Seeking is not reliable because I don't know how to keep track of the * difference between the time which MOC is currently playing and how much * time has been precached... :-| * * Generic seeking can't be done because the whole audio would have to be * replayed until the position is reached (which would introduce a delay). * */ extern "C" int sidplay2_seek (void *, int) { return -1; } extern "C" int sidplay2_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct sidplay2_data *data = (struct sidplay2_data *)void_data; int seconds = data->player->time() / data->player->timebase(); int currentLength = data->sublengths[data->currentSong-1]; if(seconds >= currentLength) { if(data->currentSong >= data->timeEnd) return 0; data->player->stop(); data->currentSong++; data->tune->selectSong(data->currentSong); data->player->load(data->tune); currentLength = data->sublengths[data->currentSong-1]; seconds = 0; } sound_params->channels = data->channels; sound_params->rate = data->frequency; sound_params->fmt = data->sample_format; return data->player->play((void *)buf, buf_len); } extern "C" int sidplay2_get_bitrate (void *) { return -1; } extern "C" int sidplay2_get_duration (void *void_data) { struct sidplay2_data *data = (struct sidplay2_data *)void_data; return data->length; } extern "C" int sidplay2_our_format_ext(const char *ext) { return !strcasecmp (ext, "SID") || !strcasecmp (ext, "MUS"); } extern "C" void init() { defaultLength = options_get_int(OPT_DEFLEN); minLength = options_get_int(OPT_MINLEN); startAtStart = options_get_bool(OPT_START); playSubTunes = options_get_bool(OPT_SUBTUNES); database = NULL; init_db = 1; playerIndex = POOL_SIZE-1; /* turns to 0 at first use */ } extern "C" void destroy() { pthread_mutex_destroy(&db_mtx); pthread_mutex_destroy(&player_select_mtx); if(database!=NULL) delete database; for(int i=0; i < POOL_SIZE; i++) { if(players[i]!=NULL) delete players[i]; if(builders[i]!=NULL) delete builders[i]; } } static struct decoder sidplay2_decoder = { DECODER_API_VERSION, init, destroy, sidplay2_open, NULL, NULL, sidplay2_close, sidplay2_decode, sidplay2_seek, sidplay2_info, sidplay2_get_bitrate, sidplay2_get_duration, sidplay2_get_error, sidplay2_our_format_ext, NULL, NULL, NULL, NULL, NULL }; extern "C" struct decoder *plugin_init () { pthread_mutex_init(&db_mtx, NULL); pthread_mutex_init(&player_select_mtx, NULL); return &sidplay2_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/sidplay2/sidplay2.h0000664000076400000500000000451512747016164021741 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * libsidplay2-plugin Copyright (C) 2007 Hendrik Iben * Enables MOC to play sids via libsidplay2/libsidutils. * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __cplusplus extern "C" { #endif #include "decoder.h" #ifdef __cplusplus } #endif #ifdef __cplusplus // debug and error are used by this library too... #undef debug #undef error #include #include #include #include #define RESID_ID "ReSID" #define OPT_DEFLEN "SidPlay2_DefaultSongLength" #define OPT_MINLEN "SidPlay2_MinimumSongLength" #define OPT_DATABASE "SidPlay2_Database" #define OPT_FREQ "SidPlay2_Frequency" #define OPT_PREC "SidPlay2_Bits" #define OPT_PMODE "SidPlay2_PlayMode" #define OPT_OPTI "SidPlay2_Optimisation" #define OPT_START "SidPlay2_StartAtStart" #define OPT_SUBTUNES "SidPlay2_PlaySubTunes" #define STITLE 0 #define SAUTHOR 1 #define SCOPY 2 #define POOL_SIZE 2 struct sidplay2_data { SidTuneMod * tune; SID_EXTERN::sidplay2 *player; sid2_config_t cfg; ReSIDBuilder *builder; int length; int *sublengths; int songs; int startSong; int currentSong; int timeStart; int timeEnd; struct decoder_error error; int sample_format, frequency, channels; }; #endif #ifdef __cplusplus extern "C" { #endif void *sidplay2_open (const char *file); void sidplay2_close (void *void_data); void sidplay2_get_error (void *prv_data, struct decoder_error *error); void sidplay2_info (const char *file_name, struct file_tags *info, const int tags_sel); int sidplay2_seek (void *void_data, int sec); int sidplay2_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params); int sidplay2_get_bitrate (void *void_data); int sidplay2_get_duration (void *void_data); void sidplay2_get_name (const char *file, char buf[4]); int sidplay2_our_format_ext (const char *ext); void destroy (); void init (); decoder *plugin_init (); #ifdef __cplusplus } #endif moc-2.6.0~svn-r3005/decoder_plugins/sidplay2/sidplay2.m40000664000076400000500000000247713160323143022023 0ustar riesebiesrcdnl libsidplay2 AC_ARG_WITH(sidplay2, AS_HELP_STRING([--without-sidplay2], [Compile without libsidplay2])) if test "x$with_sidplay2" != "xno" then PKG_CHECK_MODULES(sidplay2, libsidplay2 >= 2.1.1, [sidplay2_OK="yes"], [true]) PKG_CHECK_MODULES(sidutils, libsidutils >= 1.0.4, [sidutils_OK="yes"], [true]) dnl This is a rather ugly hack to find the builder dnl as libsidplay2 works fine without it but the dnl decoder uses it... if test "x$sidplay2_OK" = "xyes"; then if test "x$sidutils_OK" = "xyes"; then s2lib=`$PKG_CONFIG --variable=builders libsidplay2 2>/dev/null` if test "x$s2lib" != "x"; then AC_LANG_PUSH([C++]) save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $sidplay2_CFLAGS" AC_CHECK_HEADER([sidplay/builders/resid.h], [resid_OK="yes"]) CXXFLAGS="$save_CXXFLAGS" AC_LANG_POP([C++]) if test "x$resid_OK" = "xyes"; then sidplay2_LDFLAGS="-L$s2lib -lresid-builder" AC_SUBST(sidplay2_LDFLAGS) AC_SUBST(sidplay2_LIBS) AC_SUBST(sidplay2_CFLAGS) AC_SUBST(sidutils_LIBS) AC_SUBST(sidutils_CFLAGS) want_sidplay2="yes" DECODER_PLUGINS="$DECODER_PLUGINS sidplay2" fi fi fi fi fi AM_CONDITIONAL([BUILD_sidplay2], [test "$want_sidplay2"]) AC_CONFIG_FILES([decoder_plugins/sidplay2/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/sndfile/0002775000076400000500000000000013710016723017723 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/sndfile/Makefile.am0000664000076400000500000000043711601243316021756 0ustar riesebiesrclib_LTLIBRARIES = libsndfile_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libsndfile_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libsndfile_decoder_la_LIBADD = $(sndfile_LIBS) libsndfile_decoder_la_CFLAGS = $(sndfile_CFLAGS) -I$(top_srcdir) libsndfile_decoder_la_SOURCES = sndfile.c moc-2.6.0~svn-r3005/decoder_plugins/sndfile/sndfile.c0000664000076400000500000001677013123411535021522 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "decoder.h" #include "server.h" #include "log.h" #include "files.h" #include "lists.h" /* TODO: * - sndfile is not thread-safe: use a mutex? * - some tags can be read. */ struct sndfile_data { SNDFILE *sndfile; SF_INFO snd_info; struct decoder_error error; bool timing_broken; }; static lists_t_strs *supported_extns = NULL; static void load_extn_list () { const int counts[] = {SFC_GET_SIMPLE_FORMAT_COUNT, SFC_GET_FORMAT_MAJOR_COUNT}; const int formats[] = {SFC_GET_SIMPLE_FORMAT, SFC_GET_FORMAT_MAJOR}; supported_extns = lists_strs_new (16); for (size_t ix = 0; ix < ARRAY_SIZE(counts); ix += 1) { int limit; SF_FORMAT_INFO format_info; sf_command (NULL, counts[ix], &limit, sizeof (limit)); for (int iy = 0 ; iy < limit ; iy += 1) { format_info.format = iy ; sf_command (NULL, formats[ix], &format_info, sizeof (format_info)); if (!lists_strs_exists (supported_extns, format_info.extension)) lists_strs_append (supported_extns, format_info.extension); } } /* These are synonyms of supported extensions. */ if (lists_strs_exists (supported_extns, "aiff")) lists_strs_append (supported_extns, "aif"); if (lists_strs_exists (supported_extns, "au")) lists_strs_append (supported_extns, "snd"); if (lists_strs_exists (supported_extns, "wav")) { lists_strs_append (supported_extns, "nist"); lists_strs_append (supported_extns, "sph"); } if (lists_strs_exists (supported_extns, "iff")) lists_strs_append (supported_extns, "svx"); if (lists_strs_exists (supported_extns, "oga")) lists_strs_append (supported_extns, "ogg"); if (lists_strs_exists (supported_extns, "sf")) lists_strs_append (supported_extns, "ircam"); if (lists_strs_exists (supported_extns, "mat")) { lists_strs_append (supported_extns, "mat4"); lists_strs_append (supported_extns, "mat5"); } } static void sndfile_init () { load_extn_list (); } static void sndfile_destroy () { lists_strs_free (supported_extns); } /* Return true iff libsndfile's frame count is unknown or miscalculated. */ static bool is_timing_broken (int fd, struct sndfile_data *data) { int rc; struct stat buf; SF_INFO *info = &data->snd_info; if (info->frames == SF_COUNT_MAX) return true; if (info->frames / info->samplerate > INT32_MAX) return true; /* The libsndfile code warns of miscalculation for huge files of * specific formats, but it's unclear if others are known to work * or the test is just omitted for them. We'll assume they work * until it's shown otherwise. */ switch (info->format & SF_FORMAT_TYPEMASK) { case SF_FORMAT_AIFF: case SF_FORMAT_AU: case SF_FORMAT_SVX: case SF_FORMAT_WAV: rc = fstat (fd, &buf); if (rc == -1) { log_errno ("Can't stat file", errno); /* We really need to return "unknown" here. */ return false; } if (buf.st_size > UINT32_MAX) return true; } return false; } static void *sndfile_open (const char *file) { int fd; struct sndfile_data *data; data = (struct sndfile_data *)xmalloc (sizeof(struct sndfile_data)); decoder_error_init (&data->error); memset (&data->snd_info, 0, sizeof(data->snd_info)); data->sndfile = NULL; data->timing_broken = false; fd = open (file, O_RDONLY); if (fd == -1) { char *err = xstrerror (errno); decoder_error (&data->error, ERROR_FATAL, 0, "Can't open file: %s", err); free (err); return data; } /* sf_open_fd() close()s 'fd' on error and in sf_close(). */ data->sndfile = sf_open_fd (fd, SFM_READ, &data->snd_info, SF_TRUE); if (!data->sndfile) { /* FIXME: sf_strerror is not thread safe with NULL argument */ decoder_error (&data->error, ERROR_FATAL, 0, "Can't open file: %s", sf_strerror(NULL)); return data; } /* If the timing is broken, sndfile only decodes up to the broken value. */ data->timing_broken = is_timing_broken (fd, data); if (data->timing_broken) { decoder_error (&data->error, ERROR_FATAL, 0, "File too large for audio format!"); return data; } debug ("Opened file %s", file); debug ("Channels: %d", data->snd_info.channels); debug ("Format: %08X", data->snd_info.format); debug ("Sample rate: %d", data->snd_info.samplerate); return data; } static void sndfile_close (void *void_data) { struct sndfile_data *data = (struct sndfile_data *)void_data; if (data->sndfile) sf_close (data->sndfile); decoder_error_clear (&data->error); free (data); } static void sndfile_info (const char *file_name, struct file_tags *info, const int tags_sel) { if (tags_sel & TAGS_TIME) { struct sndfile_data *data; data = sndfile_open (file_name); if (data->sndfile && !data->timing_broken) info->time = data->snd_info.frames / data->snd_info.samplerate; sndfile_close (data); } } static int sndfile_seek (void *void_data, int sec) { struct sndfile_data *data = (struct sndfile_data *)void_data; int res; assert (sec >= 0); res = sf_seek (data->sndfile, data->snd_info.samplerate * sec, SEEK_SET); if (res < 0) return -1; return res / data->snd_info.samplerate; } static int sndfile_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct sndfile_data *data = (struct sndfile_data *)void_data; sound_params->channels = data->snd_info.channels; sound_params->rate = data->snd_info.samplerate; sound_params->fmt = SFMT_FLOAT; return sf_readf_float (data->sndfile, (float *)buf, buf_len / sizeof(float) / data->snd_info.channels) * sizeof(float) * data->snd_info.channels; } static int sndfile_get_bitrate (void *unused ATTR_UNUSED) { return -1; } static int sndfile_get_duration (void *void_data) { int result; struct sndfile_data *data = (struct sndfile_data *)void_data; result = -1; if (!data->timing_broken) result = data->snd_info.frames / data->snd_info.samplerate; return result; } static void sndfile_get_name (const char *file, char buf[4]) { char *ext; ext = ext_pos (file); if (ext) { if (!strcasecmp (ext, "snd")) strcpy (buf, "AU"); else if (!strcasecmp (ext, "8svx")) strcpy (buf, "SVX"); else if (!strcasecmp (ext, "oga")) strcpy (buf, "OGG"); else if (!strcasecmp (ext, "sf") || !strcasecmp (ext, "icram")) strcpy (buf, "IRC"); else if (!strcasecmp (ext, "mat4") || !strcasecmp (ext, "mat5")) strcpy (buf, "MAT"); } } static int sndfile_our_format_ext (const char *ext) { return lists_strs_exists (supported_extns, ext); } static void sndfile_get_error (void *prv_data, struct decoder_error *error) { struct sndfile_data *data = (struct sndfile_data *)prv_data; decoder_error_copy (error, &data->error); } static struct decoder sndfile_decoder = { DECODER_API_VERSION, sndfile_init, sndfile_destroy, sndfile_open, NULL, NULL, sndfile_close, sndfile_decode, sndfile_seek, sndfile_info, sndfile_get_bitrate, sndfile_get_duration, sndfile_get_error, sndfile_our_format_ext, NULL, sndfile_get_name, NULL, NULL, NULL }; struct decoder *plugin_init () { return &sndfile_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/sndfile/sndfile.m40000664000076400000500000000075111611375037021616 0ustar riesebiesrcdnl libsndfile AC_ARG_WITH(sndfile, AS_HELP_STRING([--without-sndfile], [Compile without libsndfile])) if test "x$with_sndfile" != "xno" then PKG_CHECK_MODULES(sndfile, sndfile >= 1.0.0, [AC_SUBST(sndfile_LIBS) AC_SUBST(sndfile_CFLAGS) want_sndfile="yes" DECODER_PLUGINS="$DECODER_PLUGINS sndfile"], [true]) fi AM_CONDITIONAL([BUILD_sndfile], [test "$want_sndfile"]) AC_CONFIG_FILES([decoder_plugins/sndfile/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/speex/0002775000076400000500000000000013710016723017423 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/speex/Makefile.am0000664000076400000500000000041710242453053021455 0ustar riesebiesrclib_LTLIBRARIES = libspeex_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libspeex_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libspeex_decoder_la_LIBADD = $(speex_LIBS) libspeex_decoder_la_CFLAGS = $(speex_CFLAGS) -I$(top_srcdir) libspeex_decoder_la_SOURCES = speex.c moc-2.6.0~svn-r3005/decoder_plugins/speex/speex.c0000664000076400000500000003707313333460625020730 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * * Based on (and includes code from) ogg123 copyright by * Stan Seibert AND OTHER CONTRIBUTORS * and speexdec copyright by Jean-Marc Valin */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include /*#define DEBUG*/ #include "common.h" #include "decoder.h" #include "io.h" #include "audio.h" #include "log.h" /* Use speex's audio enhancement feature */ #define ENHANCE_AUDIO 1 struct spx_data { struct io_stream *stream; struct decoder_error error; int ok; /* was the stream opened succesfully? */ SpeexBits bits; void *st; /* speex decoder state */ ogg_sync_state oy; ogg_page og; ogg_packet op; ogg_stream_state os; SpeexStereoState stereo; SpeexHeader *header; int frame_size; int rate; int nchannels; int frames_per_packet; int bitrate; int16_t *output; int output_start; int output_left; char *comment_packet; int comment_packet_len; }; static void *process_header (struct spx_data *data) { void *st; const SpeexMode *mode; int modeID; SpeexCallback callback; int enhance = ENHANCE_AUDIO; data->header = speex_packet_to_header ((char*)data->op.packet, data->op.bytes); if (!data->header) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex file: can't read header"); return NULL; } if (data->header->mode >= SPEEX_NB_MODES) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex file: Mode number %"PRId32 " does not exist in this version", data->header->mode); return NULL; } modeID = data->header->mode; mode = speex_mode_list[modeID]; if (mode->bitstream_version < data->header->mode_bitstream_version) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex file: The file was encoded " "with a newer version of Speex."); return NULL; } if (mode->bitstream_version > data->header->mode_bitstream_version) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex file: The file was encoded " "with an older version of Speex."); return NULL; } st = speex_decoder_init (mode); speex_decoder_ctl(st, SPEEX_SET_ENH, &enhance); speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, &data->frame_size); callback.callback_id = SPEEX_INBAND_STEREO; callback.func = speex_std_stereo_request_handler; callback.data = &data->stereo; speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback); speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, &data->header->rate); return st; } /* Read the speex header. Return 0 on error. */ static int read_speex_header (struct spx_data *data) { int packet_count = 0; int stream_init = 0; char *buf; ssize_t nb_read; int header_packets = 2; while (packet_count < header_packets) { /* Get the ogg buffer for writing */ buf = ogg_sync_buffer (&data->oy, 200); /* Read bitstream from input file */ nb_read = io_read (data->stream, buf, 200); if (nb_read < 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex file: IO error: %s", io_strerror(data->stream)); return 0; } if (nb_read == 0) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't open speex header"); return 0; } ogg_sync_wrote (&data->oy, nb_read); /* Loop for all complete pages we got (most likely only one) */ while (ogg_sync_pageout(&data->oy, &data->og) == 1) { if (stream_init == 0) { ogg_stream_init(&data->os, ogg_page_serialno(&data->og)); stream_init = 1; } /* Add page to the bitstream */ ogg_stream_pagein (&data->os, &data->og); /* Extract all available packets FIXME: EOS! */ while (ogg_stream_packetout(&data->os, &data->op) == 1) { /* If first packet, process as Speex header */ if (packet_count == 0) { data->st = process_header (data); if (!data->st) { ogg_stream_clear (&data->os); return 0; } data->rate = data->header->rate; data->nchannels = data->header->nb_channels; data->frames_per_packet = data->header->frames_per_packet; /*data->vbr = data->header->vbr; */ if (!data->frames_per_packet) data->frames_per_packet=1; data->output = xmalloc (data->frame_size * data->nchannels * data->frames_per_packet * sizeof(int16_t)); data->output_start = 0; data->output_left = 0; header_packets += data->header->extra_headers; } else if (packet_count == 1) { data->comment_packet_len = data->op.bytes; data->comment_packet = xmalloc ( sizeof(char) * data->comment_packet_len); memcpy (data->comment_packet, data->op.packet, data->comment_packet_len); } packet_count++; } } } return 1; } static struct spx_data *spx_open_internal (struct io_stream *stream) { struct spx_data *data; SpeexStereoState stereo = SPEEX_STEREO_STATE_INIT; data = (struct spx_data *)xmalloc (sizeof(struct spx_data)); decoder_error_init (&data->error); data->stream = stream; data->st = NULL; data->stereo = stereo; data->header = NULL; data->output = NULL; data->comment_packet = NULL; data->bitrate = -1; ogg_sync_init (&data->oy); speex_bits_init (&data->bits); if (!read_speex_header(data)) { ogg_sync_clear (&data->oy); speex_bits_destroy (&data->bits); data->ok = 0; } else data->ok = 1; return data; } static void *spx_open (const char *file) { struct io_stream *stream; struct spx_data *data; stream = io_open (file, 1); if (io_ok (stream)) data = spx_open_internal (stream); else { data = (struct spx_data *)xmalloc (sizeof(struct spx_data)); data->stream = stream; data->header = NULL; decoder_error_init (&data->error); decoder_error (&data->error, ERROR_STREAM, 0, "Can't open file: %s", io_strerror(stream)); data->ok = 0; } return data; } static void *spx_open_stream (struct io_stream *stream) { return spx_open_internal (stream); } static int spx_can_decode (struct io_stream *stream) { char buf[36]; if (io_peek(stream, buf, 36) == 36 && !memcmp(buf, "OggS", 4) && !memcmp(buf + 28, "Speex ", 8)) return 1; return 0; } static void spx_close (void *prv_data) { struct spx_data *data = (struct spx_data *)prv_data; if (data->ok) { if (data->st) speex_decoder_destroy (data->st); if (data->comment_packet) free (data->comment_packet); if (data->output) free (data->output); speex_bits_destroy (&data->bits); ogg_stream_clear (&data->os); ogg_sync_clear (&data->oy); } io_close (data->stream); decoder_error_clear (&data->error); free (data->header); free (data); } #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \ ((buf[base+2]<<16)&0xff0000)| \ ((buf[base+1]<<8)&0xff00)| \ (buf[base]&0xff)) static void parse_comment (const char *str, struct file_tags *tags) { if (!strncasecmp(str, "title=", strlen ("title="))) tags->title = xstrdup(str + strlen ("title=")); else if (!strncasecmp(str, "artist=", strlen ("artist="))) tags->artist = xstrdup (str + strlen ("artist=")); else if (!strncasecmp(str, "album=", strlen ("album="))) tags->album = xstrdup (str + strlen ("album=")); else if (!strncasecmp(str, "tracknumber=", strlen ("tracknumber="))) tags->track = atoi (str + strlen ("tracknumber=")); else if (!strncasecmp(str, "track=", strlen ("track="))) tags->track = atoi (str + strlen ("track=")); } static void get_comments (struct spx_data *data, struct file_tags *tags) { if (data->comment_packet && data->comment_packet_len >= 8) { char *c = data->comment_packet; int len, i, nb_fields; char *end; char *temp = NULL; int temp_len = 0; /* Parse out vendor string */ end = c + data->comment_packet_len; len = readint(c, 0); c += 4; if (c + len > end) { logit ("Broken comment"); return; } c += len; if (c + 4 > end) { logit ("Broken comment"); return; } nb_fields = readint (c, 0); c += 4; for (i = 0; i < nb_fields; i++) { if (c + 4 > end) { if (temp) free (temp); logit ("Broken comment"); return; } len = readint (c, 0); c += 4; if (c + len > end) { logit ("Broken comment"); if (temp) free (temp); return; } if (temp_len < len + 1) { temp_len = len + 1; temp = xrealloc (temp, temp_len); } strncpy (temp, c, len); temp[len] = '\0'; debug ("COMMENT: '%s'", temp); parse_comment (temp, tags); c += len; } if (temp) free(temp); } } static void get_more_data (struct spx_data *data) { char *buf; ssize_t nb_read; buf = ogg_sync_buffer (&data->oy, 200); nb_read = io_read (data->stream, buf, 200); ogg_sync_wrote (&data->oy, nb_read); } static int count_time (struct spx_data *data) { ogg_int64_t last_granulepos = 0; /* Seek to somewhere near the last page */ if (io_file_size(data->stream) > 10000) { debug ("Seeking near the end"); if (io_seek(data->stream, -10000, SEEK_END) == -1) logit ("Seeking failed, scanning whole file"); ogg_sync_reset (&data->oy); } /* Read granulepos from the last packet */ while (!io_eof(data->stream)) { /* Sync to page and read it */ while (!io_eof(data->stream)) { if (ogg_sync_pageout(&data->oy, &data->og) == 1) { debug ("Sync"); break; } if (!io_eof(data->stream)) { debug ("Need more data"); get_more_data (data); } } /* We have last packet */ if (io_eof(data->stream)) break; last_granulepos = ogg_page_granulepos (&data->og); } return last_granulepos / data->rate; } /* Fill info structure with data from spx comments */ static void spx_info (const char *file_name, struct file_tags *tags, const int tags_sel) { struct io_stream *s; s = io_open (file_name, 0); if (io_ok (s)) { struct spx_data *data = spx_open_internal (s); if (data->ok) { if (tags_sel & TAGS_COMMENTS) get_comments (data, tags); if (tags_sel & TAGS_TIME) tags->time = count_time (data); } spx_close (data); } else io_close (s); } static int spx_seek (void *prv_data, int sec) { struct spx_data *data = (struct spx_data *)prv_data; off_t begin = 0, end, old_pos; assert (sec >= 0); end = io_file_size (data->stream); if (end == -1) return -1; old_pos = io_tell (data->stream); debug ("Seek request to %ds", sec); while (1) { off_t middle = (end + begin) / 2; ogg_int64_t granule_pos; int position_seconds; debug ("Seek to %"PRId64, middle); if (io_seek(data->stream, middle, SEEK_SET) == -1) { io_seek (data->stream, old_pos, SEEK_SET); ogg_stream_reset (&data->os); ogg_sync_reset (&data->oy); return -1; } debug ("Syncing..."); /* Sync to page and read it */ ogg_sync_reset (&data->oy); while (!io_eof(data->stream)) { if (ogg_sync_pageout(&data->oy, &data->og) == 1) { debug ("Sync"); break; } if (!io_eof(data->stream)) { debug ("Need more data"); get_more_data (data); } } if (io_eof(data->stream)) { debug ("EOF when syncing"); return -1; } granule_pos = ogg_page_granulepos(&data->og); position_seconds = granule_pos / data->rate; debug ("We are at %ds", position_seconds); if (position_seconds == sec) { ogg_stream_pagein (&data->os, &data->og); debug ("We have it at granulepos %"PRId64, granule_pos); break; } else if (sec < position_seconds) { end = middle; debug ("going back"); } else { begin = middle; debug ("going forward"); } debug ("begin - end %"PRId64" - %"PRId64, begin, end); if (end - begin <= 200) { /* Can't find the exact position. */ sec = position_seconds; break; } } ogg_sync_reset (&data->oy); ogg_stream_reset (&data->os); return sec; } static int spx_decode (void *prv_data, char *sound_buf, int nbytes, struct sound_params *sound_params) { struct spx_data *data = (struct spx_data *)prv_data; int bytes_requested = nbytes; int16_t *out = (int16_t *)sound_buf; sound_params->channels = data->nchannels; sound_params->rate = data->rate; sound_params->fmt = SFMT_S16 | SFMT_NE; while (nbytes) { int j; /* First see if there is anything left in the output buffer and * empty it out */ if (data->output_left > 0) { int to_copy = nbytes / sizeof(int16_t); to_copy = MIN(data->output_left, to_copy); memcpy (out, data->output + data->output_start, to_copy * sizeof(int16_t)); out += to_copy; data->output_start += to_copy; data->output_left -= to_copy; nbytes -= to_copy * sizeof(int16_t); } else if (ogg_stream_packetout (&data->os, &data->op) == 1) { int16_t *temp_output = data->output; /* Decode some more samples */ /* Copy Ogg packet to Speex bitstream */ speex_bits_read_from (&data->bits, (char*)data->op.packet, data->op.bytes); for (j = 0; j < data->frames_per_packet; j++) { /* Decode frame */ speex_decode_int (data->st, &data->bits, temp_output); if (data->nchannels == 2) speex_decode_stereo_int (temp_output, data->frame_size, &data->stereo); speex_decoder_ctl (data->st, SPEEX_GET_BITRATE, &data->bitrate); /*data->samples_decoded += data->frame_size;*/ temp_output += data->frame_size * data->nchannels; } /*logit ("Read %d bytes from page", data->frame_size * data->nchannels * data->frames_per_packet);*/ data->output_start = 0; data->output_left = data->frame_size * data->nchannels * data->frames_per_packet; } else if (ogg_sync_pageout(&data->oy, &data->og) == 1) { /* Read in another ogg page */ ogg_stream_pagein (&data->os, &data->og); debug ("Granulepos: %"PRId64, ogg_page_granulepos(&data->og)); } else if (!io_eof(data->stream)) { /* Finally, pull in some more data and try again on the next pass */ get_more_data (data); } else break; } return bytes_requested - nbytes; } #if 0 static int spx_current_tags (void *prv_data, struct file_tags *tags) { struct spx_data *data = (struct spx_data *)prv_data; return 0; } #endif static int spx_get_bitrate (void *prv_data) { struct spx_data *data = (struct spx_data *)prv_data; return data->bitrate / 1000; } static int spx_get_duration (void *unused ATTR_UNUSED) { /*struct spx_data *data = (struct spx_data *)prv_data;*/ return -1; } static struct io_stream *spx_get_stream (void *prv_data) { struct spx_data *data = (struct spx_data *)prv_data; return data->stream; } static void spx_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "SPX"); } static int spx_our_format_ext (const char *ext) { return !strcasecmp (ext, "spx"); } static void spx_get_error (void *prv_data, struct decoder_error *error) { struct spx_data *data = (struct spx_data *)prv_data; decoder_error_copy (error, &data->error); } static int spx_our_mime (const char *mime) { return !strcasecmp (mime, "audio/x-speex") || !strncasecmp (mime, "audio/x-speex;", 14) || !strcasecmp (mime, "audio/speex") || !strncasecmp (mime, "audio/speex;", 12); } static struct decoder spx_decoder = { DECODER_API_VERSION, NULL, NULL, spx_open, spx_open_stream, spx_can_decode, spx_close, spx_decode, spx_seek, spx_info, spx_get_bitrate, spx_get_duration, spx_get_error, spx_our_format_ext, spx_our_mime, spx_get_name, NULL /*spx_current_tags*/, spx_get_stream, NULL }; struct decoder *plugin_init () { return &spx_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/speex/speex.m40000664000076400000500000000072511611375037021017 0ustar riesebiesrcdnl speex AC_ARG_WITH(speex, AS_HELP_STRING([--without-speex], [Compile without speex support])) if test "x$with_speex" != "xno" then PKG_CHECK_MODULES(speex, [ogg >= 1.0 speex >= 1.0.0], [AC_SUBST(speex_LIBS) AC_SUBST(speex_CFLAGS) want_speex="yes" DECODER_PLUGINS="$DECODER_PLUGINS speex"], [true]) fi AM_CONDITIONAL([BUILD_speex], [test "$want_speex"]) AC_CONFIG_FILES([decoder_plugins/speex/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/timidity/0002775000076400000500000000000013710016723020133 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/timidity/Makefile.am0000664000076400000500000000044710563335575022206 0ustar riesebiesrclib_LTLIBRARIES = libtimidity_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libtimidity_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libtimidity_decoder_la_LIBADD = $(timidity_LIBS) libtimidity_decoder_la_CFLAGS = $(timidity_CFLAGS) -I$(top_srcdir) libtimidity_decoder_la_SOURCES = timidity.c moc-2.6.0~svn-r3005/decoder_plugins/timidity/timidity.c0000664000076400000500000001264613321755547022156 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * libTiMidity-plugin Copyright (C) 2007 Hendrik Iben * The hard work is done by the libTiMidity-Library written by * Konstantin Korikov (http://libtimidity.sourceforge.net). * I have merely hacked together a wrapper... * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #define DEBUG #include "common.h" #include "io.h" #include "decoder.h" #include "log.h" #include "files.h" #include "options.h" MidSongOptions midioptions; struct timidity_data { MidSong *midisong; int length; struct decoder_error error; }; static struct timidity_data *make_timidity_data(const char *file) { struct timidity_data *data; data = (struct timidity_data *)xmalloc (sizeof(struct timidity_data)); data->midisong = NULL; decoder_error_init (&data->error); MidIStream *midistream = mid_istream_open_file(file); if(midistream==NULL) { decoder_error(&data->error, ERROR_FATAL, 0, "Can't open midifile: %s", file); return data; } data->midisong = mid_song_load(midistream, &midioptions); mid_istream_close(midistream); if(data->midisong==NULL) { decoder_error(&data->error, ERROR_FATAL, 0, "Can't load midifile: %s", file); return data; } return data; } static void *timidity_open (const char *file) { struct timidity_data *data = make_timidity_data(file); if(data->midisong) { data->length = mid_song_get_total_time(data->midisong); } if(data->midisong) { debug ("Opened file %s", file); mid_song_set_volume(data->midisong, options_get_int("TiMidity_Volume")); mid_song_start(data->midisong); } return data; } static void timidity_close (void *void_data) { struct timidity_data *data = (struct timidity_data *)void_data; if (data->midisong) { mid_song_free(data->midisong); } decoder_error_clear (&data->error); free (data); } static void timidity_info (const char *file_name, struct file_tags *info, const int tags_sel) { struct timidity_data *data = make_timidity_data(file_name); if(data->midisong==NULL) { free (data); return; } if(tags_sel & TAGS_TIME) { info->time = mid_song_get_total_time(data->midisong) / 1000; info->filled |= TAGS_TIME; } timidity_close(data); } static int timidity_seek (void *void_data, int sec) { struct timidity_data *data = (struct timidity_data *)void_data; assert (sec >= 0); int ms = sec*1000; ms = MIN(ms,data->length); mid_song_seek(data->midisong, ms); return ms/1000; } static int timidity_decode (void *void_data, char *buf, int buf_len, struct sound_params *sound_params) { struct timidity_data *data = (struct timidity_data *)void_data; sound_params->channels = midioptions.channels; sound_params->rate = midioptions.rate; sound_params->fmt = (midioptions.format==MID_AUDIO_S16LSB)?(SFMT_S16 | SFMT_LE):SFMT_S8; return mid_song_read_wave(data->midisong, (void *)buf, buf_len); } static int timidity_get_bitrate (void *unused ATTR_UNUSED) { return -1; } static int timidity_get_duration (void *void_data) { struct timidity_data *data = (struct timidity_data *)void_data; return data->length/1000; } static void timidity_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "MID"); } static int timidity_our_format_ext(const char *ext) { return !strcasecmp (ext, "MID"); } static int timidity_our_format_mime (const char *mime) { return !strcasecmp(mime, "audio/midi") || !strncasecmp(mime, "audio/midi;", 10); } static void timidity_get_error (void *prv_data, struct decoder_error *error) { struct timidity_data *data = (struct timidity_data *)prv_data; decoder_error_copy (error, &data->error); } static void timidity_destroy() { mid_exit(); } static struct decoder timidity_decoder = { DECODER_API_VERSION, NULL, timidity_destroy, timidity_open, NULL, NULL, timidity_close, timidity_decode, timidity_seek, timidity_info, timidity_get_bitrate, timidity_get_duration, timidity_get_error, timidity_our_format_ext, timidity_our_format_mime, timidity_get_name, NULL, NULL, NULL }; struct decoder *plugin_init () { char *config; int initresult; config = options_get_str("TiMidity_Config"); if (config == NULL || strcasecmp(config, "yes") == 0) initresult = mid_init(NULL); else if (strcasecmp(config, "no") == 0) initresult = mid_init_no_config(); else initresult = mid_init(config); // Is there a better way to signal failed init? // The decoder-init-function may not return errors AFAIK... if(initresult < 0) { if (config == NULL || strcasecmp(config, "yes") == 0) config = ""; fatal("TiMidity-Plugin: Error processing TiMidity-Configuration!\n" " Configuration file is: %s", config); } midioptions.rate = options_get_int("TiMidity_Rate"); midioptions.format = (options_get_int("TiMidity_Bits")==16)?MID_AUDIO_S16LSB:MID_AUDIO_S8; midioptions.channels = options_get_int("TiMidity_Channels"); midioptions.buffer_size = midioptions.rate; return &timidity_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/timidity/timidity.m40000664000076400000500000000077311611375037022242 0ustar riesebiesrcdnl libtimidity AC_ARG_WITH(timidity, AS_HELP_STRING([--without-timidity], [Compile without libtimidity])) if test "x$with_timidity" != "xno" then PKG_CHECK_MODULES(timidity, libtimidity >= 0.1.0, [AC_SUBST(timidity_LIBS) AC_SUBST(timidity_CFLAGS) want_timidity="yes" DECODER_PLUGINS="$DECODER_PLUGINS timidity"], [true]) fi AM_CONDITIONAL([BUILD_timidity], [test "$want_timidity"]) AC_CONFIG_FILES([decoder_plugins/timidity/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/vorbis/0002775000076400000500000000000013710016723017603 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/vorbis/Makefile.am0000664000076400000500000000043710356176713021652 0ustar riesebiesrclib_LTLIBRARIES = libvorbis_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libvorbis_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libvorbis_decoder_la_LIBADD = $(OGG_VORBIS_LIBS) libvorbis_decoder_la_CFLAGS = $(OGG_VORBIS_CFLAGS) -I$(top_srcdir) libvorbis_decoder_la_SOURCES = vorbis.c moc-2.6.0~svn-r3005/decoder_plugins/vorbis/vorbis.c0000664000076400000500000002526312705572631021271 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2002 - 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #ifndef HAVE_TREMOR #include #include #else #include #include #endif #define DEBUG #include "common.h" #include "log.h" #include "decoder.h" #include "io.h" #include "audio.h" /* These merely silence compiler warnings about unused definitions in * the Vorbis library header files. */ #if defined(HAVE_VAR_ATTRIBUTE_UNUSED) && !defined(HAVE_TREMOR) static ov_callbacks *vorbis_unused[] ATTR_UNUSED = { &OV_CALLBACKS_DEFAULT, &OV_CALLBACKS_NOCLOSE, &OV_CALLBACKS_STREAMONLY, &OV_CALLBACKS_STREAMONLY_NOCLOSE }; #endif /* Tremor defines time as 64-bit integer milliseconds. */ #ifndef HAVE_TREMOR static const int64_t time_scaler = 1; #else static const int64_t time_scaler = 1000; #endif struct vorbis_data { struct io_stream *stream; OggVorbis_File vf; int last_section; int bitrate; int avg_bitrate; int duration; struct decoder_error error; int ok; /* was this stream successfully opened? */ int tags_change; /* the tags were changed from the last call of ogg_current_tags() */ struct file_tags *tags; }; static void get_comment_tags (OggVorbis_File *vf, struct file_tags *info) { int i; vorbis_comment *comments; comments = ov_comment (vf, -1); for (i = 0; i < comments->comments; i++) { if (!strncasecmp(comments->user_comments[i], "title=", strlen ("title="))) info->title = xstrdup(comments->user_comments[i] + strlen ("title=")); else if (!strncasecmp(comments->user_comments[i], "artist=", strlen ("artist="))) info->artist = xstrdup ( comments->user_comments[i] + strlen ("artist=")); else if (!strncasecmp(comments->user_comments[i], "album=", strlen ("album="))) info->album = xstrdup ( comments->user_comments[i] + strlen ("album=")); else if (!strncasecmp(comments->user_comments[i], "tracknumber=", strlen ("tracknumber="))) info->track = atoi (comments->user_comments[i] + strlen ("tracknumber=")); else if (!strncasecmp(comments->user_comments[i], "track=", strlen ("track="))) info->track = atoi (comments->user_comments[i] + strlen ("track=")); } } /* Return a description of an ov_*() error. */ static const char *vorbis_strerror (const int code) { const char *result; switch (code) { case OV_EREAD: result = "read error"; break; case OV_ENOTVORBIS: result = "not a vorbis file"; break; case OV_EVERSION: result = "vorbis version mismatch"; break; case OV_EBADHEADER: result = "invalid Vorbis bitstream header"; break; case OV_EFAULT: result = "internal (vorbis) logic fault"; break; default: result = "unknown error"; } return result; } /* Fill info structure with data from ogg comments */ static void vorbis_tags (const char *file_name, struct file_tags *info, const int tags_sel) { OggVorbis_File vf; FILE *file; int err_code; if (!(file = fopen (file_name, "r"))) { log_errno ("Can't open an OGG file", errno); return; } /* ov_test() is faster than ov_open(), but we can't read file time * with it. */ if (tags_sel & TAGS_TIME) err_code = ov_open(file, &vf, NULL, 0); else err_code = ov_test(file, &vf, NULL, 0); if (err_code < 0) { logit ("Can't open %s: %s", file_name, vorbis_strerror (err_code)); fclose (file); return; } if (tags_sel & TAGS_COMMENTS) get_comment_tags (&vf, info); if (tags_sel & TAGS_TIME) { int64_t vorbis_time; vorbis_time = ov_time_total (&vf, -1); if (vorbis_time >= 0) info->time = vorbis_time / time_scaler; } ov_clear (&vf); } static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *datasource) { ssize_t res; res = io_read (datasource, ptr, size * nmemb); /* libvorbisfile expects the read callback to return >= 0 with errno * set to non zero on error. */ if (res < 0) { logit ("Read error"); if (errno == 0) errno = 0xffff; res = 0; } else res /= size; return res; } static int seek_cb (void *datasource, ogg_int64_t offset, int whence) { debug ("Seek request to %"PRId64" (%s)", offset, whence == SEEK_SET ? "SEEK_SET" : (whence == SEEK_CUR ? "SEEK_CUR" : "SEEK_END")); return io_seek (datasource, offset, whence) == -1 ? -1 : 0; } static int close_cb (void *unused ATTR_UNUSED) { return 0; } static long tell_cb (void *datasource) { return (long)io_tell (datasource); } static void vorbis_open_stream_internal (struct vorbis_data *data) { int res; ov_callbacks callbacks = { read_cb, seek_cb, close_cb, tell_cb }; data->tags = tags_new (); res = ov_open_callbacks (data->stream, &data->vf, NULL, 0, callbacks); if (res < 0) { const char *vorbis_err = vorbis_strerror (res); decoder_error (&data->error, ERROR_FATAL, 0, "%s", vorbis_err); debug ("ov_open error: %s", vorbis_err); } else { int64_t duration; data->last_section = -1; data->avg_bitrate = ov_bitrate (&data->vf, -1) / 1000; data->bitrate = data->avg_bitrate; data->duration = -1; duration = ov_time_total (&data->vf, -1); if (duration >= 0) data->duration = duration / time_scaler; data->ok = 1; get_comment_tags (&data->vf, data->tags); } } static void *vorbis_open (const char *file) { struct vorbis_data *data; data = (struct vorbis_data *)xmalloc (sizeof(struct vorbis_data)); data->ok = 0; decoder_error_init (&data->error); data->tags_change = 0; data->tags = NULL; data->stream = io_open (file, 1); if (!io_ok(data->stream)) { decoder_error (&data->error, ERROR_FATAL, 0, "Can't load OGG: %s", io_strerror(data->stream)); return data; } /* This a restriction placed on us by the vorbisfile API. */ #if INT64_MAX > LONG_MAX if (io_file_size (data->stream) > LONG_MAX) { decoder_error (&data->error, ERROR_FATAL, 0, "File too large!"); return data; } #endif vorbis_open_stream_internal (data); return data; } static int vorbis_can_decode (struct io_stream *stream) { char buf[35]; if (io_peek (stream, buf, 35) == 35 && !memcmp (buf, "OggS", 4) && !memcmp (buf + 28, "\01vorbis", 7)) return 1; return 0; } static void *vorbis_open_stream (struct io_stream *stream) { struct vorbis_data *data; data = (struct vorbis_data *)xmalloc (sizeof(struct vorbis_data)); data->ok = 0; decoder_error_init (&data->error); data->stream = stream; vorbis_open_stream_internal (data); return data; } static void vorbis_close (void *prv_data) { struct vorbis_data *data = (struct vorbis_data *)prv_data; if (data->ok) { ov_clear (&data->vf); } io_close (data->stream); decoder_error_clear (&data->error); if (data->tags) tags_free (data->tags); free (data); } static int vorbis_seek (void *prv_data, int sec) { struct vorbis_data *data = (struct vorbis_data *)prv_data; assert (sec >= 0); return ov_time_seek (&data->vf, sec * time_scaler) ? -1 : sec; } static int vorbis_decode (void *prv_data, char *buf, int buf_len, struct sound_params *sound_params) { struct vorbis_data *data = (struct vorbis_data *)prv_data; int ret; int current_section; int bitrate; vorbis_info *info; decoder_error_clear (&data->error); while (1) { #ifndef HAVE_TREMOR ret = ov_read(&data->vf, buf, buf_len, (SFMT_NE == SFMT_LE ? 0 : 1), 2, 1, ¤t_section); #else ret = ov_read(&data->vf, buf, buf_len, ¤t_section); #endif if (ret == 0) return 0; if (ret < 0) { decoder_error (&data->error, ERROR_STREAM, 0, "Error in the stream!"); continue; } if (current_section != data->last_section) { logit ("section change or first section"); data->last_section = current_section; data->tags_change = 1; tags_free (data->tags); data->tags = tags_new (); get_comment_tags (&data->vf, data->tags); } info = ov_info (&data->vf, -1); assert (info != NULL); sound_params->channels = info->channels; sound_params->rate = info->rate; sound_params->fmt = SFMT_S16 | SFMT_NE; /* Update the bitrate information */ bitrate = ov_bitrate_instant (&data->vf); if (bitrate > 0) data->bitrate = bitrate / 1000; break; } return ret; } static int vorbis_current_tags (void *prv_data, struct file_tags *tags) { struct vorbis_data *data = (struct vorbis_data *)prv_data; tags_copy (tags, data->tags); if (data->tags_change) { data->tags_change = 0; return 1; } return 0; } static int vorbis_get_bitrate (void *prv_data) { struct vorbis_data *data = (struct vorbis_data *)prv_data; return data->bitrate; } static int vorbis_get_avg_bitrate (void *prv_data) { struct vorbis_data *data = (struct vorbis_data *)prv_data; return data->avg_bitrate; } static int vorbis_get_duration (void *prv_data) { struct vorbis_data *data = (struct vorbis_data *)prv_data; return data->duration; } static struct io_stream *vorbis_get_stream (void *prv_data) { struct vorbis_data *data = (struct vorbis_data *)prv_data; return data->stream; } static void vorbis_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "OGG"); } static int vorbis_our_format_ext (const char *ext) { return !strcasecmp (ext, "ogg") || !strcasecmp (ext, "oga"); } static void vorbis_get_error (void *prv_data, struct decoder_error *error) { struct vorbis_data *data = (struct vorbis_data *)prv_data; decoder_error_copy (error, &data->error); } static int vorbis_our_mime (const char *mime) { return !strcasecmp (mime, "application/ogg") || !strncasecmp (mime, "application/ogg;", 16) || !strcasecmp (mime, "application/x-ogg") || !strncasecmp (mime, "application/x-ogg;", 18); } static struct decoder vorbis_decoder = { DECODER_API_VERSION, NULL, NULL, vorbis_open, vorbis_open_stream, vorbis_can_decode, vorbis_close, vorbis_decode, vorbis_seek, vorbis_tags, vorbis_get_bitrate, vorbis_get_duration, vorbis_get_error, vorbis_our_format_ext, vorbis_our_mime, vorbis_get_name, vorbis_current_tags, vorbis_get_stream, vorbis_get_avg_bitrate }; struct decoder *plugin_init () { return &vorbis_decoder; } /* Defined and true if the Vorbis decoder is using Tremor, otherwise * undefined. This is used by the decoder plugin loader so it can * document which library is being used without requiring the decoder * and the loader be built with the same HAVE_TREMOR setting. */ #ifdef HAVE_TREMOR const bool vorbis_has_tremor = true; #endif moc-2.6.0~svn-r3005/decoder_plugins/vorbis/vorbis.m40000664000076400000500000000156711742710625021365 0ustar riesebiesrcdnl vorbis AC_ARG_WITH(vorbis, AS_HELP_STRING([--without-vorbis], [Compile without Ogg Vorbis support])) if test "x$with_vorbis" == "xtremor" then PKG_CHECK_MODULES(OGG_VORBIS, [vorbisidec >= 1.0], [AC_SUBST(OGG_VORBIS_LIBS) AC_SUBST(OGG_VORBIS_CFLAGS) AC_DEFINE([HAVE_TREMOR], 1, [Define if you integer Vorbis.]) want_vorbis="yes" DECODER_PLUGINS="$DECODER_PLUGINS vorbis(tremor)"], [true]) else if test "x$with_vorbis" != "xno" then PKG_CHECK_MODULES(OGG_VORBIS, [ogg >= 1.0 vorbis >= 1.0 vorbisfile >= 1.0], [AC_SUBST(OGG_VORBIS_LIBS) AC_SUBST(OGG_VORBIS_CFLAGS) want_vorbis="yes" DECODER_PLUGINS="$DECODER_PLUGINS vorbis"], [true]) fi fi AM_CONDITIONAL([BUILD_vorbis], [test "$want_vorbis"]) AC_CONFIG_FILES([decoder_plugins/vorbis/Makefile]) moc-2.6.0~svn-r3005/decoder_plugins/wavpack/0002775000076400000500000000000013710016723017733 5ustar riesebiesrcmoc-2.6.0~svn-r3005/decoder_plugins/wavpack/Makefile.am0000664000076400000500000000043710612413513021765 0ustar riesebiesrclib_LTLIBRARIES = libwavpack_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libwavpack_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libwavpack_decoder_la_LIBADD = $(WAVPACK_LIBS) libwavpack_decoder_la_CFLAGS = $(WAVPACK_CFLAGS) -I$(top_srcdir) libwavpack_decoder_la_SOURCES = wavpack.c moc-2.6.0~svn-r3005/decoder_plugins/wavpack/wavpack.c0000664000076400000500000001622013012456773021542 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * libwavpack-plugin Copyright (C) 2006 Alexandrov Sergey * Enables MOC to play wavpack files (actually just a wrapper around * wavpack library). * * Structure of this plugin is an adaption of the libvorbis-plugin from * moc. * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #define DEBUG #include "common.h" /* for xmalloc(), xstrdup() etc. */ #include "log.h" /* for logit() and debug() */ #include "decoder.h" /* required: provides decoder structure definition */ #include "io.h" /* if you use io_*() functions to access files. */ #include "audio.h" /* for sound_params structure */ struct wavpack_data { WavpackContext *wpc; int sample_num; int sample_rate; int avg_bitrate; int channels; int duration; int mode; struct decoder_error error; int ok; /* was this stream successfully opened? */ }; static void wav_data_init (struct wavpack_data *data) { data->sample_num = WavpackGetNumSamples (data->wpc); data->sample_rate = WavpackGetSampleRate (data->wpc); data->channels = WavpackGetReducedChannels (data->wpc); data->duration = data->sample_num / data->sample_rate; data->mode = WavpackGetMode (data->wpc); data->avg_bitrate = WavpackGetAverageBitrate (data->wpc, 1) / 1000; data->ok = 1; debug ("File opened. S_n %d. S_r %d. Time %d. Avg_Bitrate %d.", data->sample_num, data->sample_rate, data->duration, data->avg_bitrate ); } static void *wav_open (const char *file) { struct wavpack_data *data; data = (struct wavpack_data *)xmalloc (sizeof(struct wavpack_data)); data->ok = 0; decoder_error_init (&data->error); int o_flags = OPEN_2CH_MAX | OPEN_WVC; char wv_error[100]; if ((data->wpc = WavpackOpenFileInput(file, wv_error, o_flags, 0)) == NULL) { decoder_error (&data->error, ERROR_FATAL, 0, "%s", wv_error); logit ("wv_open error: %s", wv_error); } else wav_data_init (data); return data; } static void wav_close (void *prv_data) { struct wavpack_data *data = (struct wavpack_data *)prv_data; if (data->ok) { WavpackCloseFile (data->wpc); } decoder_error_clear (&data->error); free (data); logit ("File closed"); } static int wav_seek (void *prv_data, int sec) { struct wavpack_data *data = (struct wavpack_data *)prv_data; assert (sec >= 0); if (WavpackSeekSample (data->wpc, sec * data->sample_rate)) return sec; decoder_error (&data->error, ERROR_FATAL, 0, "Fatal seeking error!"); return -1; } static int wav_get_bitrate (void *prv_data) { struct wavpack_data *data = (struct wavpack_data *)prv_data; int bitrate; bitrate = WavpackGetInstantBitrate (data->wpc) / 1000; return (bitrate == 0)? data->avg_bitrate : bitrate; } static int wav_get_avg_bitrate (void *prv_data) { struct wavpack_data *data = (struct wavpack_data *)prv_data; return data->avg_bitrate; } static int wav_get_duration (void *prv_data) { struct wavpack_data *data = (struct wavpack_data *)prv_data; return data->duration; } static void wav_get_error (void *prv_data, struct decoder_error *error) { struct wavpack_data *data = (struct wavpack_data *)prv_data; decoder_error_copy (error, &data->error); } static void wav_info (const char *file_name, struct file_tags *info, const int tags_sel) { char wv_error[100]; char *tag; int tag_len; WavpackContext *wpc; wpc = WavpackOpenFileInput (file_name, wv_error, OPEN_TAGS, 0); if (wpc == NULL) { logit ("wv_open error: %s", wv_error); return; } int duration = WavpackGetNumSamples (wpc) / WavpackGetSampleRate (wpc); if(tags_sel & TAGS_TIME) { info->time = duration; info->filled |= TAGS_TIME; } if(tags_sel & TAGS_COMMENTS) { if ((tag_len = WavpackGetTagItem (wpc, "title", NULL, 0)) > 0) { info->title = (char *)xmalloc (++tag_len); WavpackGetTagItem (wpc, "title", info->title, tag_len); } if ((tag_len = WavpackGetTagItem (wpc, "artist", NULL, 0)) > 0) { info->artist = (char *)xmalloc (++tag_len); WavpackGetTagItem (wpc, "artist", info->artist, tag_len); } if ((tag_len = WavpackGetTagItem (wpc, "album", NULL, 0)) > 0) { info->album = (char *)xmalloc (++tag_len); WavpackGetTagItem (wpc, "album", info->album, tag_len); } if ((tag_len = WavpackGetTagItem (wpc, "track", NULL, 0)) > 0) { tag = (char *)xmalloc (++tag_len); WavpackGetTagItem (wpc, "track", tag, tag_len); info->track = atoi (tag); free (tag); } info->filled |= TAGS_COMMENTS; } WavpackCloseFile (wpc); } static int wav_decode (void *prv_data, char *buf, int buf_len, struct sound_params *sound_params) { struct wavpack_data *data = (struct wavpack_data *)prv_data; int ret, i, s_num, iBps, oBps; int8_t *buf8 = (int8_t *)buf; int16_t *buf16 = (int16_t *)buf; int32_t *buf32 = (int32_t *)buf; iBps = data->channels * WavpackGetBytesPerSample (data->wpc); oBps = (iBps == 6) ? 8 : iBps; s_num = buf_len / oBps; decoder_error_clear (&data->error); int32_t *dbuf = (int32_t *)xcalloc (s_num, data->channels * 4); ret = WavpackUnpackSamples (data->wpc, dbuf, s_num); if (ret == 0) { free (dbuf); return 0; } if (data->mode & MODE_FLOAT) { sound_params->fmt = SFMT_FLOAT; memcpy (buf, dbuf, ret * oBps); } else { debug ("iBps %d", iBps); switch (iBps / data->channels){ case 4: for (i = 0; i < ret * data->channels; i++) buf32[i] = dbuf[i]; sound_params->fmt = SFMT_S32 | SFMT_NE; break; case 3: for (i = 0; i < ret * data->channels; i++) buf32[i] = dbuf[i] * 256; sound_params->fmt = SFMT_S32 | SFMT_NE; break; case 2: for (i = 0; i < ret * data->channels; i++) buf16[i] = dbuf[i]; sound_params->fmt = SFMT_S16 | SFMT_NE; break; case 1: for (i = 0; i < ret * data->channels; i++) buf8[i] = dbuf[i]; sound_params->fmt = SFMT_S8 | SFMT_NE; } } sound_params->channels = data->channels; sound_params->rate = data->sample_rate; free (dbuf); return ret * oBps ; } static int wav_our_mime (const char *mime ATTR_UNUSED) { /* We don't support internet streams for now. */ #if 0 return !strcasecmp (mime, "audio/x-wavpack") || !strncasecmp (mime, "audio/x-wavpack;", 16) #endif return 0; } static void wav_get_name (const char *unused ATTR_UNUSED, char buf[4]) { strcpy (buf, "WV"); } static int wav_our_format_ext(const char *ext) { return !strcasecmp (ext, "WV"); } static struct decoder wv_decoder = { DECODER_API_VERSION, NULL,//wav_init NULL,//wav_destroy wav_open, NULL,//wav_open_stream, NULL,//wav_can_decode, wav_close, wav_decode, wav_seek, wav_info, wav_get_bitrate, wav_get_duration, wav_get_error, wav_our_format_ext, wav_our_mime, wav_get_name, NULL,//wav_current_tags, NULL,//wav_get_stream wav_get_avg_bitrate }; struct decoder *plugin_init () { return &wv_decoder; } moc-2.6.0~svn-r3005/decoder_plugins/wavpack/wavpack.m40000664000076400000500000000073511611375037021640 0ustar riesebiesrcdnl wavpack AC_ARG_WITH(wavpack, AS_HELP_STRING([--without-wavpack], [Compile without WavPack support])) if test "x$with_wavpack" != "xno" then PKG_CHECK_MODULES(WAVPACK, [wavpack >= 4.31], [AC_SUBST(WAVPACK_LIBS) AC_SUBST(WAVPACK_CFLAGS) want_wavpack="yes" DECODER_PLUGINS="$DECODER_PLUGINS wavpack"], [true]) fi AM_CONDITIONAL([BUILD_wavpack], [test "$want_wavpack"]) AC_CONFIG_FILES([decoder_plugins/wavpack/Makefile]) moc-2.6.0~svn-r3005/doxy_pages/0002775000076400000500000000000013710016723015273 5ustar riesebiesrcmoc-2.6.0~svn-r3005/doxy_pages/decoder_api.doxy0000664000076400000500000002123413012456764020446 0ustar riesebiesrc/** \page decoder_api Decoder API \section decoder_api_introduction Introduction This document is intended to make it easier to add support for new file formats to MOC. It documents the requirements of decoder plugins and some useful functions. It also says how to start. \section decoder_plugin_idea The idea Each file format is supported by code in a plugin. It's something similar to plugins in popular audio players like XMMS with exception that there is no separate library and dedicated header files to build such a plugin. It means that to create a plugin you need to add it to MOC's source directory. It will be built with the program and a file named like my_plugin.so will be created and placed in the plugin directory during installation. So why plugins? The idea is to avoid the dependency of the mocp binary on various libraries used just to support some exotic formats. If a plugin with an unresolved dependency is found at startup it's just not loaded. \section decoder_plugin_how_to_add_source How to add the plugin code Let's create a plugin named myplug. First create a directory in the decoder_plugins directory named myplug; you will place the sources there. Let's have 2 source files: myplug1.c and myplug2.c in your plugin's directory. Now you must create a Makefile.am file there that contains the following lines: \code lib_LTLIBRARIES = libmyplug_decoder.la libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) libmyplug_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ libmyplug_decoder_la_LIBADD = $(MYPLUG_LIBS) libmyplug_decoder_la_CFLAGS = $(MYPLUG_CFLAGS) -I$(top_srcdir) libmyplug_decoder_la_SOURCES = myplug1.c myplug2.c \endcode Next you have to tell the build system to include your newly created directory by editting the file Makefile.am in the decoder_plugins directory. You'll see plenty of examples there, but your new entry will look like this: \code if BUILD_myplug SUBDIRS += myplug endif \endcode Almost done, now the hard part. As you may have already noticed, the code in Makefile.am uses two useful variables: MYPLUG_CFLAGS and MYPLUG_LIBS. They contain additional compiler and linker flags used to compile your plugin. They are set by the configure script. You must include a configure script fragment (myplug.m4 in this case) in your myplug directory and modify decoder_plugins/decoders.m4 to include it. The fragment needs to detect libraries and compiler flags needed to build the plugin, add myplug to the DECODER_PLUGINS variable and set things up so the plugin can be built. As an example, let's look at the code detecting libFLAC which is using an autoconf macro provided by the FLAC library and is very simple: \code AC_ARG_WITH(flac, AS_HELP_STRING([--without-flac], [Compile without FLAC support])) if test "x$with_flac" != "xno" then AM_PATH_LIBFLAC([AC_SUBST(LIBFLAC_LIBS) AC_SUBST(LIBFLAC_CFLAGS) $want_flac="yes" DECODER_PLUGINS="$DECODER_PLUGINS flac"]) fi AM_CONDITIONAL([BUILD_flac], [test "$want_flac"]) AC_CONFIG_FILES([decoder_plugins/flac/Makefile]) \endcode If your decoder uses packages which don't have pkg-config configurations, you can use something like this: \code AC_ARG_WITH(myplug, AS_HELP_STRING([--without-myplug], [Compile without MyPlug support])) if test "x$with_myplug" != "xno" then AC_CHECK_LIB(required_library, SomeFunction, [myplug_OK="yes"]) if test "x$myplug_OK" = "xyes" then MYPLUG_LIBS='-lrequired_library' AC_SUBST(MYPLUG_LIBS) MYPLUG_CFLAGS='-I/usr/include/required_library_includes' AC_SUBST(MYPLUG_CFLAGS) $want_myplug="yes" DECODER_PLUGINS="$DECODER_PLUGINS myplug" fi fi AM_CONDITIONAL([BUILD_myplug], [test "$want_flac"]) AC_CONFIG_FILES([decoder_plugins/myplug/Makefile]) \endcode In the absence of any library dependencies, this reduces to: \code AC_ARG_WITH(myplug, AS_HELP_STRING([--without-myplug], [Compile without MyPlug support])) if test "x$with_myplug" != "xno" then MYPLUG_LIBS='-lrequired_library' AC_SUBST(MYPLUG_LIBS) MYPLUG_CFLAGS='-I/usr/include/required_library_includes' AC_SUBST(MYPLUG_CFLAGS) $want_myplug="yes" DECODER_PLUGINS="$DECODER_PLUGINS myplug" fi AM_CONDITIONAL([BUILD_myplug], [test "$want_flac"]) AC_CONFIG_FILES([decoder_plugins/myplug/Makefile]) \endcode Now you must recreate the configure script and all Makefile.in files: run 'autoreconf [-i]' (it requires autoconf, automake and libtool -- the newest versions of these tools are preferred; older versions may not work). \section decoder_plugin_how_to_begin First code The only public (not static) function that your plugin must contain is a function named plugin_init. It returns a structure (struct decoder) with pointers to functions provided by the plugin. Let's look at ogg's plugin code: \code static struct decoder ogg_decoder = { DECODER_API_VERSION, ogg_open, ogg_open_stream, ogg_can_decode, ogg_close, ogg_decode, ogg_seek, ogg_info, ogg_get_bitrate, ogg_get_duration, ogg_get_error, ogg_our_format_ext, ogg_our_mime, ogg_get_name, ogg_current_tags, ogg_get_stream }; struct decoder *plugin_init () { return &ogg_decoder; } \endcode This is something that every plugin must have. Also, the first lines of code in each C source file should be: \code #ifdef HAVE_CONFIG_H #include "config.h" #endif \endcode You may place comments above them (for example, containing copyright information). Includes you need are: \verbatim #include "main.h" /* for xmalloc(), xstrdup() etc. */ #include "log.h" /* for logit() and debug() */ #include "decoder.h" /* required: provides decoder structure definition */ #include "io.h" /* if you use io_*() functions to access files. */ #include "audio.h" /* for sound_params structure */ \endverbatim \section decoder_plugin_requirements Requirements First thing you must remember is that your plugin is running in multi-thread program. When it is asked to open a file it must return a pointer to the structure containing your private data. This pointer will be passed to each function associated with the stream. You must not use any global or static (in function) variables, because more than one file your plugin handles may be open at a time (for example, when precaching). When you operate on a file it is preferred (but not required) that you use io_*() functions from io.h instead of standard open(), read(), write() etc. MOC's I/O functions provide sophisticated features: buffering in a separate thread and reading from Internet streams. They were made to be used like standard UNIX input/output functions: they take similar parameters and return similar values. \section decoder_plugin_useful_api The API Now it's time to read the documentation and find out what all that functions must do and what functions you may use to achieve that. \li \ref decoder \li \ref decoder_error_funcs For debugging purposes you may use two macros: debug() and logit() from log.h. The first one does nothing if you don't define DEBUG before including log.h, and works like logit() if you do. logit() works like printf(), but it only prints to the log file when the --debug command line option is used. Also, logit() includes time and source file name of the function name where it was invoked. You don't need to add the end of line character to make lines, each invocation of logit() makes a new line. It is also preferred that you use xmalloc(), xrealloc(), xstrdup() functions from common.h instead of those without x. They check if allocation of memory succeeds, so you don't need to do that. \section decoder_plugin_useful_testing Testing and Debugging There is a script in the 'tools' subdirectory which will help you to test your decoder. It checks that the samples which arrive at MOC's player code are the same as those produced by the library the decoder is using. It does this by having MOC compute the MD5 sum of the audio file as it is playing it and writing the result to the server's log file. The log file is then read by the 'md5check.sh' tool and the MD5 sum checked against one computed from the audio file using the library's native decoding program. You will need to add a new function and case item to call it to the script so it can recognise your new decoder's library. It is important to use the program associated with the decoder library if one exists because other programs may not produce exactly the same samples, particularly for lossy formats. It is also possible that bugs in the library mean the samples produced are not correct, but the tool is intended to test the passage of the samples through the driver and not the fidelity of the library used. */ moc-2.6.0~svn-r3005/doxy_pages/main_page.doxy0000664000076400000500000000121011665254127020121 0ustar riesebiesrc/** \mainpage Internal API documentation \author Damian Pietras \section introduction Introduction This document describes the part of the internal API that can be used to add support for a new file format or sound output method. Everything that is required or may be useful is documented, the rest changes too often. I hope this documentation is up-to-date enough and contains sufficiently few errors to make it useful... . \section documented_parts Documented parts of the API There are two main parts of the API you in which you could be interested: \li \ref decoder_api \li \ref sound_output_driver_api */ moc-2.6.0~svn-r3005/doxy_pages/sound_output_driver_api.doxy0000664000076400000500000000051311665254127023162 0ustar riesebiesrc/** \page sound_output_driver_api Sound output driver API \section sound_output_driver_api_introduction Introduction This is the sound output driver API. Sorry, no introduction here, you must see hw_funcs description and some examples. (null_out.c does nothing, but it's the simplest driver and can be used as a template.) */ moc-2.6.0~svn-r3005/equalizer.c0000664000076400000500000005640312643042200015275 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004-2008 Damian Pietras * * Equalizer-extension Copyright (C) 2008 Hendrik Iben * Provides a parametric biquadratic equalizer. * * This code is based on the 'Cookbook formulae for audio EQ biquad filter * coefficients' by Robert Bristow-Johnson. * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt * * TODO: * - Merge somehow with softmixer code to avoid multiple endianness * conversions. * - Implement equalizer routines for integer samples... conversion * to float (and back) is lazy... * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "audio.h" #include "audio_conversion.h" #include "options.h" #include "log.h" #include "files.h" #include "equalizer.h" #define TWOPI (2.0 * M_PI) #define NEWLINE 0x0A #define CRETURN 0x0D #define SPACE 0x20 #define EQSET_HEADER "EQSET" #define EQUALIZER_CFG_ACTIVE "Active:" #define EQUALIZER_CFG_PRESET "Preset:" #define EQUALIZER_CFG_MIXIN "Mixin:" #define EQUALIZER_SAVE_FILE "equalizer" #define EQUALIZER_SAVE_OPTION "Equalizer_SaveState" typedef struct t_biquad t_biquad; struct t_biquad { float a0, a1, a2, a3, a4; float x1, x2, y1, y2; float cf, bw, gain, srate; int israte; }; typedef struct t_eq_setup t_eq_setup; struct t_eq_setup { char *name; float preamp; int bcount; float *cf; float *bw; float *dg; }; typedef struct t_eq_set t_eq_set; struct t_eq_set { char *name; int channels; float preamp; int bcount; t_biquad *b; }; typedef struct t_eq_set_list t_eq_set_list; struct t_eq_set_list { t_eq_set *set; t_eq_set_list *prev, *next; }; typedef struct t_active_set t_active_set; struct t_active_set { int srate; t_eq_set *set; }; typedef struct t_eq_settings t_eq_settings; struct t_eq_settings { char *preset_name; int bcount; float *gain; t_eq_settings *next; }; /* config processing */ static char *skip_line(char *s); static char *skip_whitespace(char *s); static int read_float(char *s, float *f, char **endp); static int read_setup(char *name, char *desc, t_eq_setup **sp); static void equalizer_adjust_preamp(); static void equalizer_read_config(); static void equalizer_write_config(); /* biquad application */ static inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen); /* biquad filter creation */ static t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b); /* equalizer list processing */ static t_eq_set_list *append_eq_set(t_eq_set *eqs, t_eq_set_list *l); static void clear_eq_set(t_eq_set_list *l); /* sound processing */ static void equ_process_buffer_u8(uint8_t *buf, size_t samples); static void equ_process_buffer_s8(int8_t *buf, size_t samples); static void equ_process_buffer_u16(uint16_t *buf, size_t samples); static void equ_process_buffer_s16(int16_t *buf, size_t samples); static void equ_process_buffer_u32(uint32_t *buf, size_t samples); static void equ_process_buffer_s32(int32_t *buf, size_t samples); static void equ_process_buffer_float(float *buf, size_t samples); /* static global variables */ static t_eq_set_list equ_list, *current_equ; static int sample_rate, equ_active, equ_channels; static float mixin_rate, r_mixin_rate; static float preamp, preampf; static char *eqsetdir; static char *config_preset_name; /* public functions */ int equalizer_is_active() { return equ_active?1:0; } int equalizer_set_active(int active) { return equ_active = active?1:0; } char *equalizer_current_eqname() { if(equ_active && current_equ && current_equ->set) { return xstrdup(current_equ->set->name); } return xstrdup("off"); } void equalizer_next() { if(current_equ) { if(current_equ->next) { current_equ = current_equ->next; } else { current_equ = &equ_list; } if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next)) equalizer_next(); } equalizer_adjust_preamp(); } void equalizer_prev() { if(current_equ) { if(current_equ->prev) { current_equ = current_equ->prev; } else { while(current_equ->next) current_equ = current_equ->next; } if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next)) equalizer_prev(); } equalizer_adjust_preamp(); } /* biquad functions */ /* Create a Peaking EQ Filter. * See 'Audio EQ Cookbook' for more information */ static t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b) { if(b==NULL) b = (t_biquad *)xmalloc(sizeof(t_biquad)); float A = powf(10.0f, dbgain / 40.0f); float omega = TWOPI * cf / srate; float sn = sin(omega); float cs = cos(omega); float alpha = sn * sinh(M_LN2 / 2.0f * bw * omega / sn); float alpha_m_A = alpha * A; float alpha_d_A = alpha / A; float b0 = 1.0f + alpha_m_A; float b1 = -2.0f * cs; float b2 = 1.0f - alpha_m_A; float a0 = 1.0f + alpha_d_A; float a1 = b1; float a2 = 1.0f - alpha_d_A; b->a0 = b0 / a0; b->a1 = b1 / a0; b->a2 = b2 / a0; b->a3 = a1 / a0; b->a4 = a2 / a0; b->x1 = 0.0f; b->x2 = 0.0f; b->y1 = 0.0f; b->y2 = 0.0f; b->cf = cf; b->bw = bw; b->srate = srate; b->israte = (int)srate; b->gain = dbgain; return b; } /* * not used but keep as example use for biquad filter static inline void biquad(float *src, float *dst, int len, t_biquad *b) { while(len-->0) { float s = *src++; float f = s * b->a0 + b->a1 * b->x1 + b->a2 * b->x2 - b->a3 * b->y1 - b->a4 * b->y2; *dst++=f; b->x2 = b->x1; b->x1 = s; b->y2 = b->y1; b->y1 = f; } } */ /* Applies a set of biquadratic filters to a buffer of floating point * samples. * It is safe to have the same input and output buffer. * * blen is the sample-count ignoring channels (samples per channel * channels) */ static inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen) { int bi, ci, boffs, idx; while(len>0) { boffs = 0; for(ci=0; ciset) { preamp = current_equ->set->preamp; preampf = powf(10.0f, current_equ->set->preamp / 20.0f); } } static void equalizer_read_config() { char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // posix decimal point char *sfile = xstrdup(create_file_name("equalizer")); FILE *cf = fopen(sfile, "r"); free (sfile); if(cf==NULL) { logit ("Unable to read equalizer configuration"); if (curloc) free (curloc); return; } char *linebuffer = NULL; char presetbuf[128]; presetbuf[0] = 0; int tmp; float ftmp; while((linebuffer=read_line(cf))) { if( strncasecmp ( linebuffer , EQUALIZER_CFG_ACTIVE , strlen(EQUALIZER_CFG_ACTIVE) ) == 0 ) { if(sscanf(linebuffer, "%*s %i", &tmp)>0) { if(tmp>0) { equ_active = 1; } else { equ_active = 0; } } } if( strncasecmp ( linebuffer , EQUALIZER_CFG_MIXIN , strlen(EQUALIZER_CFG_MIXIN) ) == 0 ) { if(sscanf(linebuffer, "%*s %f", &ftmp)>0) { if(RANGE(0.0f, ftmp, 1.0f)) { mixin_rate = ftmp; } } } if( strncasecmp ( linebuffer , EQUALIZER_CFG_PRESET , strlen(EQUALIZER_CFG_PRESET) ) == 0 ) { if(sscanf(linebuffer, "%*s %127s", presetbuf)>0) { /* ignore too large strings... */ if(strlen(presetbuf)<127) { if(config_preset_name) free(config_preset_name); config_preset_name = xstrdup(presetbuf); } } } free(linebuffer); } fclose(cf); if (curloc) { setlocale(LC_NUMERIC, curloc); free (curloc); } } static void equalizer_write_config() { char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); /* posix decimal point */ char *cfname = create_file_name(EQUALIZER_SAVE_FILE); FILE *cf = fopen(cfname, "w"); if(cf==NULL) { logit ("Unable to write equalizer configuration"); if (curloc) free (curloc); return; } fprintf(cf, "%s %i\n", EQUALIZER_CFG_ACTIVE, equ_active); if(current_equ && current_equ->set) fprintf(cf, "%s %s\n", EQUALIZER_CFG_PRESET, current_equ->set->name); fprintf(cf, "%s %f\n", EQUALIZER_CFG_MIXIN, mixin_rate); fclose(cf); if (curloc) { setlocale(LC_NUMERIC, curloc); free (curloc); } logit ("Equalizer configuration written"); } void equalizer_init() { equ_active = 1; equ_list.set = NULL; equ_list.next = NULL; equ_list.prev = NULL; sample_rate = 44100; equ_channels = 2; preamp = 0.0f; preampf = powf(10.0f, preamp / 20.0f); eqsetdir = xstrdup(create_file_name("eqsets")); config_preset_name = NULL; mixin_rate = 0.25f; equalizer_read_config(); r_mixin_rate = 1.0f - mixin_rate; equalizer_refresh(); logit ("Equalizer initialized"); } void equalizer_shutdown() { if(options_get_bool(EQUALIZER_SAVE_OPTION)) equalizer_write_config(); clear_eq_set(&equ_list); logit ("Equalizer stopped"); } void equalizer_refresh() { t_eq_setup *eqs = NULL; char buf[1024]; char *current_set_name = NULL; if(current_equ && current_equ->set) { current_set_name = xstrdup(current_equ->set->name); } else { if(config_preset_name) current_set_name = xstrdup(config_preset_name); } clear_eq_set(&equ_list); current_equ = NULL; DIR *d = opendir(eqsetdir); if(!d) { return; } struct dirent *de = readdir(d); struct stat st; t_eq_set_list *last_elem; last_elem = &equ_list; while(de) { sprintf(buf, "eqsets/%s", de->d_name); char *filename = xstrdup(create_file_name(buf)); stat(filename, &st); if( S_ISREG(st.st_mode) ) { FILE *f = fopen(filename, "r"); if(f) { char filebuffer[4096]; char *fb = filebuffer; int maxread = 4095 - (fb - filebuffer); // read in whole file while(!feof(f) && maxread>0) { maxread = 4095 - (fb - filebuffer); int rb = fread(fb, sizeof(char), maxread, f); fb+=rb; } fclose(f); *fb = 0; int r = read_setup(de->d_name, filebuffer, &eqs); if(r==0) { int i, channel; t_eq_set *eqset = (t_eq_set *)xmalloc(sizeof(t_eq_set)); eqset->b = (t_biquad *)xmalloc(sizeof(t_biquad)*eqs->bcount*equ_channels); eqset->name = xstrdup(eqs->name); eqset->preamp = eqs->preamp; eqset->bcount = eqs->bcount; eqset->channels = equ_channels; for(i=0; ibcount; i++) { mk_biquad(eqs->dg[i], eqs->cf[i], sample_rate, eqs->bw[i], &eqset->b[i]); for(channel=1; channelb[channel*eqset->bcount + i] = eqset->b[i]; } } last_elem = append_eq_set(eqset, last_elem); free(eqs->name); free(eqs->cf); free(eqs->bw); free(eqs->dg); } else { switch(r) { case 0: logit ("This should not happen: No error but no EQSET was parsed: %s", filename); break; case -1: logit ("Not an EQSET (empty file): %s", filename); break; case -2: logit ("Not an EQSET (invalid header): %s", filename); break; case -3: logit ("Error while parsing settings from EQSET: %s", filename); break; default: logit ("Unknown error while parsing EQSET: %s", filename); break; } } if(eqs) free(eqs); eqs = NULL; } } free(filename); de = readdir(d); } closedir(d); current_equ = &equ_list; if(current_set_name) { current_equ = &equ_list; while(current_equ) { if(current_equ->set) { if(strcmp(current_set_name, current_equ->set->name)==0) break; } current_equ = current_equ->next; } if(current_equ) { // only free name when EQ was found to allow logging free(current_set_name); } } if (!current_equ && current_set_name) { logit ("EQ %s not found.", current_set_name); /* equalizer not found, pick next equalizer */ current_equ = &equ_list; // free name now free(current_set_name); } if(current_equ && !current_equ->set) equalizer_next(); equalizer_adjust_preamp(); } /* sound processing code */ void equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params) { debug ("EQ Processing %zu bytes...", size); if(!equ_active || !current_equ || !current_equ->set) return; if(sound_params->rate != current_equ->set->b->israte || sound_params->channels != equ_channels) { logit ("Recreating filters due to sound parameter changes..."); sample_rate = sound_params->rate; equ_channels = sound_params->channels; equalizer_refresh(); } long sound_endianness = sound_params->fmt & SFMT_MASK_ENDIANNESS; long sound_format = sound_params->fmt & SFMT_MASK_FORMAT; int samplewidth = sfmt_Bps(sound_format); int is_float = (sound_params->fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT; int need_endianness_swap = 0; if((sound_endianness != SFMT_NE) && (samplewidth > 1) && (!is_float)) { need_endianness_swap = 1; } assert (size % (samplewidth * sound_params->channels) == 0); /* setup samples to perform arithmetic */ if(need_endianness_swap) { debug ("Converting endianness before mixing"); if(samplewidth == 4) audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t)); else audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t)); } switch(sound_format) { case SFMT_U8: equ_process_buffer_u8((uint8_t *)buf, size); break; case SFMT_S8: equ_process_buffer_s8((int8_t *)buf, size); break; case SFMT_U16: equ_process_buffer_u16((uint16_t *)buf, size / sizeof(uint16_t)); break; case SFMT_S16: equ_process_buffer_s16((int16_t *)buf, size / sizeof(int16_t)); break; case SFMT_U32: equ_process_buffer_u32((uint32_t *)buf, size / sizeof(uint32_t)); break; case SFMT_S32: equ_process_buffer_s32((int32_t *)buf, size / sizeof(int32_t)); break; case SFMT_FLOAT: equ_process_buffer_float((float *)buf, size / sizeof(float)); break; } /* restore sample-endianness */ if(need_endianness_swap) { debug ("Restoring endianness after mixing"); if(samplewidth == 4) audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t)); else audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t)); } } static void equ_process_buffer_u8(uint8_t *buf, size_t samples) { size_t i; float *tmp; debug ("equalizing"); tmp = (float *)xmalloc (samples * sizeof (float)); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset->b, current_equ->set->bcount); for(i=0; iset == NULL) { l->set = eqs; } else { if(l->next) { append_eq_set(eqs, l->next); } else { l->next = (t_eq_set_list *)xmalloc(sizeof(t_eq_set_list)); l->next->set = NULL; l->next->next = NULL; l->next->prev = l; l = append_eq_set(eqs, l->next); } } return l; } static void clear_eq_set(t_eq_set_list *l) { if(l->set) { free(l->set->name); free(l->set->b); free(l->set); l->set = NULL; } if(l->next) { clear_eq_set(l->next); free(l->next); l->next = NULL; } } /* parsing stuff */ static int read_setup(char *name, char *desc, t_eq_setup **sp) { char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // posix decimal point t_eq_setup *s = *sp; desc = skip_whitespace(desc); if(!*desc) { if (curloc) free (curloc); return -1; } if(strncasecmp(desc, EQSET_HEADER, sizeof(EQSET_HEADER)-1)) { if (curloc) free (curloc); return -2; } desc+=5; desc = skip_whitespace(skip_line(desc)); if(s==NULL) { s=(t_eq_setup *)xmalloc(sizeof(t_eq_setup)); *sp = s; } s->name = xstrdup(name); s->bcount = 0; s->preamp = 0.0f; int max_values = 16; s->cf = (float *)xmalloc(max_values*sizeof(float)); s->bw = (float *)xmalloc(max_values*sizeof(float)); s->dg = (float *)xmalloc(max_values*sizeof(float)); int r; while(*desc) { char *endp; float cf = 0.0f; r = read_float(desc, &cf, &endp); if(r!=0) { free(s->name); free(s->cf); free(s->bw); free(s->dg); if (curloc) free (curloc); return -3; } desc = skip_whitespace(endp); float bw = 0.0f; r = read_float(desc, &bw, &endp); if(r!=0) { free(s->name); free(s->cf); free(s->bw); free(s->dg); if (curloc) free (curloc); return -3; } desc = skip_whitespace(endp); float dg = 0.0f; /* 0Hz means preamp, only one parameter then */ if(cf!=0.0f) { r = read_float(desc, &dg, &endp); if(r!=0) { free(s->name); free(s->cf); free(s->bw); free(s->dg); if (curloc) free (curloc); return -3; } desc = skip_whitespace(endp); if(s->bcount>=(max_values-1)) { max_values*=2; s->cf=xrealloc(s->cf, max_values*sizeof(float)); s->bw=xrealloc(s->bw, max_values*sizeof(float)); s->dg=xrealloc(s->dg, max_values*sizeof(float)); } s->cf[s->bcount]=cf; s->bw[s->bcount]=bw; s->dg[s->bcount]=dg; s->bcount++; } else { s->preamp = bw; } } if (curloc) { setlocale(LC_NUMERIC, curloc); // posix decimal point free (curloc); } return 0; } static char *skip_line(char *s) { int dos_line = 0; while(*s && (*s!=CRETURN && *s!=NEWLINE) ) s++; if(*s==CRETURN) dos_line = 1; if(*s) s++; if(dos_line && *s==NEWLINE) s++; return s; } static char *skip_whitespace(char *s) { while(*s && (*s<=SPACE)) s++; if(!*s) return s; if(*s=='#') { s = skip_line(s); s = skip_whitespace(s); } return s; } static int read_float(char *s, float *f, char **endp) { errno = 0; float t = strtof(s, endp); if(errno==ERANGE) return -1; if(*endp == s) return -2; *f = t; return 0; } moc-2.6.0~svn-r3005/equalizer.h0000664000076400000500000000066312312431723015304 0ustar riesebiesrc#ifndef EQUALIZER_H #define EQUALIZER_H #ifdef __cplusplus extern "C" { #endif void equalizer_init(); void equalizer_shutdown(); void equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params); void equalizer_refresh(); int equalizer_is_active(); int equalizer_set_active(int active); char *equalizer_current_eqname(); void equalizer_next(); void equalizer_prev(); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/fifo_buf.c0000664000076400000500000000711612713507713015065 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "common.h" #include "fifo_buf.h" struct fifo_buf { int size; /* Size of the buffer */ int pos; /* Current position */ int fill; /* Current fill */ char buf[]; /* The buffer content */ }; /* Initialize and return a new fifo_buf structure of the size requested. */ struct fifo_buf *fifo_buf_new (const size_t size) { struct fifo_buf *b; assert (size > 0); b = xmalloc (offsetof (struct fifo_buf, buf) + size); b->size = size; b->pos = 0; b->fill = 0; return b; } /* Destroy the buffer object. */ void fifo_buf_free (struct fifo_buf *b) { assert (b != NULL); free (b); } /* Put data into the buffer. Returns number of bytes actually put. */ size_t fifo_buf_put (struct fifo_buf *b, const char *data, size_t size) { size_t written = 0; assert (b != NULL); assert (b->buf != NULL); while (b->fill < b->size && written < size) { size_t write_from; size_t to_write; if (b->pos + b->fill < b->size) { write_from = b->pos + b->fill; to_write = b->size - (b->pos + b->fill); } else { write_from = b->fill - b->size + b->pos; to_write = b->size - b->fill; } if (to_write > size - written) to_write = size - written; memcpy (b->buf + write_from, data + written, to_write); b->fill += to_write; written += to_write; } return written; } /* Copy data from the beginning of the buffer to the user buffer. Returns the * number of bytes copied. */ size_t fifo_buf_peek (struct fifo_buf *b, char *user_buf, size_t user_buf_size) { size_t user_buf_pos = 0, written = 0; ssize_t left, pos; assert (b != NULL); assert (b->buf != NULL); left = b->fill; pos = b->pos; while (left && written < user_buf_size) { size_t to_copy = pos + left <= b->size ? left : b->size - pos; if (to_copy > user_buf_size - written) to_copy = user_buf_size - written; memcpy (user_buf + user_buf_pos, b->buf + pos, to_copy); user_buf_pos += to_copy; written += to_copy; left -= to_copy; pos += to_copy; if (pos == b->size) pos = 0; } return written; } size_t fifo_buf_get (struct fifo_buf *b, char *user_buf, size_t user_buf_size) { size_t user_buf_pos = 0, written = 0; assert (b != NULL); assert (b->buf != NULL); while (b->fill && written < user_buf_size) { size_t to_copy = b->pos + b->fill <= b->size ? b->fill : b->size - b->pos; if (to_copy > user_buf_size - written) to_copy = user_buf_size - written; memcpy (user_buf + user_buf_pos, b->buf + b->pos, to_copy); user_buf_pos += to_copy; written += to_copy; b->fill -= to_copy; b->pos += to_copy; if (b->pos == b->size) b->pos = 0; } return written; } /* Get the amount of free space in the buffer. */ size_t fifo_buf_get_space (const struct fifo_buf *b) { assert (b != NULL); assert (b->buf != NULL); return b->size - b->fill; } size_t fifo_buf_get_fill (const struct fifo_buf *b) { assert (b != NULL); return b->fill; } size_t fifo_buf_get_size (const struct fifo_buf *b) { assert (b != NULL); return b->size; } void fifo_buf_clear (struct fifo_buf *b) { assert (b != NULL); b->fill = 0; } moc-2.6.0~svn-r3005/fifo_buf.h0000664000076400000500000000122612424322517015062 0ustar riesebiesrc#ifndef FIFO_BUF_H #define FIFO_BUF_H #ifdef __cplusplus extern "C" { #endif struct fifo_buf; struct fifo_buf *fifo_buf_new (const size_t size); void fifo_buf_free (struct fifo_buf *b); size_t fifo_buf_put (struct fifo_buf *b, const char *data, size_t size); size_t fifo_buf_get (struct fifo_buf *b, char *user_buf, size_t user_buf_size); size_t fifo_buf_peek (struct fifo_buf *b, char *user_buf, size_t user_buf_size); size_t fifo_buf_get_space (const struct fifo_buf *b); void fifo_buf_clear (struct fifo_buf *b); size_t fifo_buf_get_fill (const struct fifo_buf *b); size_t fifo_buf_get_size (const struct fifo_buf *b); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/files.c0000664000076400000500000003761013333460607014411 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBMAGIC #include #include #endif #define DEBUG #include "common.h" #include "playlist.h" #include "lists.h" #include "interface.h" #include "decoder.h" #include "options.h" #include "files.h" #include "playlist_file.h" #include "log.h" #include "utf8.h" #define READ_LINE_INIT_SIZE 256 #ifdef HAVE_LIBMAGIC static magic_t cookie = NULL; static char *cached_file = NULL; static char *cached_result = NULL; #endif void files_init () { #ifdef HAVE_LIBMAGIC assert (cookie == NULL); cookie = magic_open (MAGIC_SYMLINK | MAGIC_MIME | MAGIC_ERROR | MAGIC_NO_CHECK_COMPRESS | MAGIC_NO_CHECK_ELF | MAGIC_NO_CHECK_TAR | MAGIC_NO_CHECK_TOKENS | MAGIC_NO_CHECK_FORTRAN | MAGIC_NO_CHECK_TROFF); if (cookie == NULL) log_errno ("Error allocating magic cookie", errno); else if (magic_load (cookie, NULL) != 0) { logit ("Error loading magic database: %s", magic_error (cookie)); magic_close (cookie); cookie = NULL; } #endif } void files_cleanup () { #ifdef HAVE_LIBMAGIC free (cached_file); cached_file = NULL; free (cached_result); cached_result = NULL; magic_close (cookie); cookie = NULL; #endif } /* Is the string a URL? */ inline int is_url (const char *str) { return !strncasecmp (str, "http://", sizeof ("http://") - 1) || !strncasecmp (str, "ftp://", sizeof ("ftp://") - 1); } /* Return 1 if the file is a directory, 0 if not, -1 on error. */ int is_dir (const char *file) { struct stat file_stat; if (is_url (file)) return 0; if (stat (file, &file_stat) == -1) { char *err = xstrerror (errno); error ("Can't stat %s: %s", file, err); free (err); return -1; } return S_ISDIR(file_stat.st_mode) ? 1 : 0; } /* Return 1 if the file can be read by this user, 0 if not */ int can_read_file (const char *file) { return access(file, R_OK) == 0; } enum file_type file_type (const char *file) { struct stat file_stat; assert (file != NULL); if (is_url(file)) return F_URL; if (stat(file, &file_stat) == -1) return F_OTHER; /* Ignore the file if stat() failed */ if (S_ISDIR(file_stat.st_mode)) return F_DIR; if (is_sound_file(file)) return F_SOUND; if (is_plist_file(file)) return F_PLAYLIST; return F_OTHER; } /* Given a file name, return the mime type or NULL. */ char *file_mime_type (const char *file ASSERT_ONLY) { char *result = NULL; assert (file != NULL); #ifdef HAVE_LIBMAGIC static pthread_mutex_t magic_mtx = PTHREAD_MUTEX_INITIALIZER; if (cookie != NULL) { LOCK(magic_mtx); if (cached_file && !strcmp (cached_file, file)) result = xstrdup (cached_result); else { free (cached_file); free (cached_result); cached_file = cached_result = NULL; result = xstrdup (magic_file (cookie, file)); if (result == NULL) logit ("Error interrogating file: %s", magic_error (cookie)); else { cached_file = xstrdup (file); cached_result = xstrdup (result); } } UNLOCK(magic_mtx); } #endif return result; } /* Make a title from the file name for the item. If hide_extn != 0, * strip the file name from extension. */ void make_file_title (struct plist *plist, const int num, const bool hide_extension) { assert (plist != NULL); assert (LIMIT(num, plist->num)); assert (!plist_deleted (plist, num)); if (file_type (plist->items[num].file) != F_URL) { char *file = xstrdup (plist->items[num].file); if (hide_extension) { char *extn; extn = ext_pos (file); if (extn) *(extn - 1) = 0; } if (options_get_bool ("FileNamesIconv")) { char *old_title = file; file = files_iconv_str (file); free (old_title); } plist_set_title_file (plist, num, file); free (file); } else plist_set_title_file (plist, num, plist->items[num].file); } /* Make a title from the tags for the item. */ void make_tags_title (struct plist *plist, const int num) { bool hide_extn; char *title; assert (plist != NULL); assert (LIMIT(num, plist->num)); assert (!plist_deleted (plist, num)); if (file_type (plist->items[num].file) == F_URL) { make_file_title (plist, num, false); return; } if (plist->items[num].title_tags) return; assert (plist->items[num].file != NULL); if (plist->items[num].tags->title) { title = build_title (plist->items[num].tags); plist_set_title_tags (plist, num, title); free (title); return; } hide_extn = options_get_bool ("HideFileExtension"); make_file_title (plist, num, hide_extn); } /* Switch playlist titles to title_file */ void switch_titles_file (struct plist *plist) { int i; bool hide_extn; hide_extn = options_get_bool ("HideFileExtension"); for (i = 0; i < plist->num; i++) { if (plist_deleted (plist, i)) continue; if (!plist->items[i].title_file) make_file_title (plist, i, hide_extn); assert (plist->items[i].title_file != NULL); } } /* Switch playlist titles to title_tags */ void switch_titles_tags (struct plist *plist) { int i; bool hide_extn; hide_extn = options_get_bool ("HideFileExtension"); for (i = 0; i < plist->num; i++) { if (plist_deleted (plist, i)) continue; if (!plist->items[i].title_tags && !plist->items[i].title_file) make_file_title (plist, i, hide_extn); } } /* Add file to the directory path in buf resolving '../' and removing './'. */ /* buf must be absolute path. */ void resolve_path (char *buf, size_t size, const char *file) { int rc; char *f; /* points to the char in *file we process */ char path[2*PATH_MAX]; /* temporary path */ size_t len = 0; /* number of characters in the buffer */ assert (buf[0] == '/'); rc = snprintf(path, sizeof(path), "%s/%s/", buf, file); if (rc >= ssizeof(path)) fatal ("Path too long!"); f = path; while (*f) { if (!strncmp(f, "/../", 4)) { char *slash = strrchr (buf, '/'); assert (slash != NULL); if (slash == buf) { /* make '/' from '/directory' */ buf[1] = 0; len = 1; } else { /* strip one element */ *(slash) = 0; len -= len - (slash - buf); buf[len] = 0; } f+= 3; } else if (!strncmp(f, "/./", 3)) /* skip '/.' */ f += 2; else if (!strncmp(f, "//", 2)) /* remove double slash */ f++; else if (len == size - 1) fatal ("Path too long!"); else { buf[len++] = *(f++); buf[len] = 0; } } /* remove dot from '/dir/.' */ if (len >= 2 && buf[len-1] == '.' && buf[len-2] == '/') buf[--len] = 0; /* strip trailing slash */ if (len > 1 && buf[len-1] == '/') buf[--len] = 0; } /* Read selected tags for a file into tags structure (or create it if NULL). * If some tags are already present, don't read them. * If present_tags is NULL, allocate new tags. */ struct file_tags *read_file_tags (const char *file, struct file_tags *tags, const int tags_sel) { struct decoder *df; int needed_tags; assert (file != NULL); if (tags == NULL) tags = tags_new (); if (file_type (file) == F_URL) return tags; needed_tags = ~tags->filled & tags_sel; if (!needed_tags) { debug ("No need to read any tags"); return tags; } df = get_decoder (file); if (!df) { logit ("Can't find decoder functions for %s", file); return tags; } /* This makes sure that we don't cause a memory leak */ assert (!((needed_tags & TAGS_COMMENTS) && (tags->title || tags->artist || tags->album))); df->info (file, tags, needed_tags); tags->filled |= tags_sel; return tags; } /* Read the content of the directory, make an array of absolute paths for * all recognized files. Put directories, playlists and sound files * in proper structures. Return 0 on error.*/ int read_directory (const char *directory, lists_t_strs *dirs, lists_t_strs *playlists, struct plist *plist) { DIR *dir; struct dirent *entry; bool show_hidden = options_get_bool ("ShowHiddenFiles"); int dir_is_root; assert (directory != NULL); assert (*directory == '/'); assert (dirs != NULL); assert (playlists != NULL); assert (plist != NULL); if (!(dir = opendir(directory))) { error_errno ("Can't read directory", errno); return 0; } if (!strcmp(directory, "/")) dir_is_root = 1; else dir_is_root = 0; while ((entry = readdir(dir))) { int rc; char file[PATH_MAX]; enum file_type type; if (user_wants_interrupt()) { error ("Interrupted! Not all files read!"); break; } if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; if (!show_hidden && entry->d_name[0] == '.') continue; rc = snprintf(file, sizeof(file), "%s/%s", dir_is_root ? "" : directory, entry->d_name); if (rc >= ssizeof(file)) { error ("Path too long!"); closedir (dir); return 0; } type = file_type (file); if (type == F_SOUND) plist_add (plist, file); else if (type == F_DIR) lists_strs_append (dirs, file); else if (type == F_PLAYLIST) lists_strs_append (playlists, file); } closedir (dir); return 1; } static int dir_symlink_loop (const ino_t inode_no, const ino_t *dir_stack, const int depth) { int i; for (i = 0; i < depth; i++) if (dir_stack[i] == inode_no) return 1; return 0; } /* Recursively add files from the directory to the playlist. * Return 1 if OK (and even some errors), 0 if the user interrupted. */ static int read_directory_recurr_internal (const char *directory, struct plist *plist, ino_t **dir_stack, int *depth) { DIR *dir; struct dirent *entry; struct stat st; if (stat (directory, &st)) { char *err = xstrerror (errno); error ("Can't stat %s: %s", directory, err); free (err); return 0; } assert (plist != NULL); assert (directory != NULL); if (*dir_stack && dir_symlink_loop(st.st_ino, *dir_stack, *depth)) { logit ("Detected symlink loop on %s", directory); return 1; } if (!(dir = opendir(directory))) { error_errno ("Can't read directory", errno); return 1; } (*depth)++; *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth)); (*dir_stack)[*depth - 1] = st.st_ino; while ((entry = readdir(dir))) { int rc; char file[PATH_MAX]; enum file_type type; if (user_wants_interrupt()) { error ("Interrupted! Not all files read!"); break; } if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; rc = snprintf(file, sizeof(file), "%s/%s", directory, entry->d_name); if (rc >= ssizeof(file)) { error ("Path too long!"); continue; } type = file_type (file); if (type == F_DIR) read_directory_recurr_internal(file, plist, dir_stack, depth); else if (type == F_SOUND && plist_find_fname(plist, file) == -1) plist_add (plist, file); } (*depth)--; *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth)); closedir (dir); return 1; } int read_directory_recurr (const char *directory, struct plist *plist) { int ret; int depth = 0; ino_t *dir_stack = NULL; ret = read_directory_recurr_internal (directory, plist, &dir_stack, &depth); if (dir_stack) free (dir_stack); return ret; } /* Return the file extension position or NULL if the file has no extension. */ char *ext_pos (const char *file) { char *ext = strrchr (file, '.'); char *slash = strrchr (file, '/'); /* don't treat dot in ./file or /.file as a dot before extension */ if (ext && (!slash || slash < ext) && ext != file && *(ext-1) != '/') ext++; else ext = NULL; return ext; } /* Read one line from a file, strip trailing end of line chars. * Returned memory is malloc()ed. Return NULL on error or EOF. */ char *read_line (FILE *file) { int line_alloc = READ_LINE_INIT_SIZE; int len = 0; char *line = (char *)xmalloc (sizeof(char) * line_alloc); while (1) { if (!fgets(line + len, line_alloc - len, file)) break; len = strlen(line); if (line[len-1] == '\n') break; /* If we are here, it means that line is longer than the buffer. */ line_alloc *= 2; line = (char *)xrealloc (line, sizeof(char) * line_alloc); } if (len == 0) { free (line); return NULL; } if (line[len-1] == '\n') line[--len] = 0; if (len > 0 && line[len-1] == '\r') line[--len] = 0; return line; } /* Return malloc()ed string in form "base/name". */ static char *add_dir_file (const char *base, const char *name) { char *path; int base_is_root; base_is_root = !strcmp (base, "/") ? 1 : 0; path = (char *)xmalloc (sizeof(char) * (strlen(base) + strlen(name) + 2)); sprintf (path, "%s/%s", base_is_root ? "" : base, name); return path; } /* Find directories having a prefix of 'pattern'. * - If there are no matches, NULL is returned. * - If there is one such directory, it is returned with a trailing '/'. * - Otherwise the longest common prefix is returned (with no trailing '/'). * (This is used for directory auto-completion.) * Returned memory is malloc()ed. * 'pattern' is temporarily modified! */ char *find_match_dir (char *pattern) { char *slash; DIR *dir; struct dirent *entry; int name_len; char *name; char *matching_dir = NULL; char *search_dir; int unambiguous = 1; if (!pattern[0]) return NULL; /* strip the last directory */ slash = strrchr (pattern, '/'); if (!slash) return NULL; if (slash == pattern) { /* only '/dir' */ search_dir = xstrdup ("/"); } else { *slash = 0; search_dir = xstrdup (pattern); *slash = '/'; } name = slash + 1; name_len = strlen (name); if (!(dir = opendir(search_dir))) return NULL; while ((entry = readdir(dir))) { if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..") && !strncmp(entry->d_name, name, name_len)) { char *path = add_dir_file (search_dir, entry->d_name); if (is_dir(path) == 1) { if (matching_dir) { /* More matching directories - strip * matching_dir to the part that is * common to both paths */ int i = 0; while (matching_dir[i] == path[i] && path[i]) i++; matching_dir[i] = 0; free (path); unambiguous = 0; } else matching_dir = path; } else free (path); } } closedir (dir); free (search_dir); if (matching_dir && unambiguous) { matching_dir = (char *)xrealloc (matching_dir, sizeof(char) * (strlen(matching_dir) + 2)); strcat (matching_dir, "/"); } return matching_dir; } /* Return != 0 if the file exists. */ int file_exists (const char *file) { struct stat file_stat; if (!stat(file, &file_stat)) return 1; /* Log any error other than non-existence. */ if (errno != ENOENT) log_errno ("Error", errno); return 0; } /* Get the modification time of a file. Return (time_t)-1 on error */ time_t get_mtime (const char *file) { struct stat stat_buf; if (stat(file, &stat_buf) != -1) return stat_buf.st_mtime; return (time_t)-1; } /* Convert file path to absolute path; * resulting string is allocated and must be freed afterwards. */ char *absolute_path (const char *path, const char *cwd) { char tmp[2*PATH_MAX]; char *result; assert (path); assert (cwd); if(path[0] != '/' && !is_url(path)) { strncpy (tmp, cwd, sizeof(tmp)); tmp[sizeof(tmp)-1] = 0; resolve_path (tmp, sizeof(tmp), path); result = (char *)xmalloc (sizeof(char) * (strlen(tmp)+1)); strcpy (result, tmp); } else { result = (char *)xmalloc (sizeof(char) * (strlen(path)+1)); strcpy (result, path); } return result; } /* Check that a file which may cause other applications to be invoked * is secure against tampering. */ bool is_secure (const char *file) { struct stat sb; assert (file && file[0]); if (stat (file, &sb) == -1) return true; if (!S_ISREG(sb.st_mode)) return false; if (sb.st_mode & (S_IWGRP|S_IWOTH)) return false; if (sb.st_uid != 0 && sb.st_uid != geteuid ()) return false; return true; } moc-2.6.0~svn-r3005/files.h0000664000076400000500000000241113333460607014405 0ustar riesebiesrc#ifndef FILES_H #define FILES_H #include #include "lists.h" #include "playlist.h" #ifdef __cplusplus extern "C" { #endif #define FILES_LIST_INIT_SIZE 64 void files_init (); void files_cleanup (); int read_directory (const char *directory, lists_t_strs *dirs, lists_t_strs *playlists, struct plist *plist); int read_directory_recurr (const char *directory, struct plist *plist); void resolve_path (char *buf, size_t size, const char *file); char *ext_pos (const char *file); enum file_type file_type (const char *file); char *file_mime_type (const char *file); int is_url (const char *str); char *read_line (FILE *file); char *find_match_dir (char *dir); int file_exists (const char *file); time_t get_mtime (const char *file); struct file_tags *read_file_tags (const char *file, struct file_tags *present_tags, const int tags_sel); void switch_titles_file (struct plist *plist); void switch_titles_tags (struct plist *plist); void make_tags_title (struct plist *plist, const int num); void make_file_title (struct plist *plist, const int num, const bool hide_extension); int is_dir (const char *file); int can_read_file (const char *file); char *absolute_path (const char *path, const char *cwd); bool is_secure (const char *file); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/interface.c0000664000076400000500000030103313333460615015237 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "log.h" #include "interface_elements.h" #include "interface.h" #include "lists.h" #include "playlist.h" #include "playlist_file.h" #include "protocol.h" #include "keys.h" #include "options.h" #include "files.h" #include "decoder.h" #include "themes.h" #include "softmixer.h" #include "utf8.h" #define INTERFACE_LOG "mocp_client_log" #define PLAYLIST_FILE "playlist.m3u" #define QUEUE_CLEAR_THRESH 128 /* Socket of the server connection. */ static int srv_sock = -1; static struct plist *playlist = NULL; /* our playlist */ static struct plist *queue = NULL; /* our queue */ static struct plist *dir_plist = NULL; /* contents of the current directory */ /* Queue for events coming from the server. */ static struct event_queue events; /* Current working directory (the directory we show). */ static char cwd[PATH_MAX] = ""; /* If the user presses quit, or we receive a termination signal. */ static volatile enum want_quit want_quit = NO_QUIT; /* If user presses CTRL-C, set this to 1. This should interrupt long * operations which block the interface. */ static volatile int wants_interrupt = 0; #ifdef SIGWINCH /* If we get SIGWINCH. */ static volatile int want_resize = 0; #endif /* Are we waiting for the playlist we have loaded and sent to the clients? */ static int waiting_for_plist_load = 0; /* Information about the currently played file. */ static struct file_info curr_file; /* Silent seeking - where we are in seconds. -1 - no seeking. */ static int silent_seek_pos = -1; static time_t silent_seek_key_last = (time_t)0; /* when the silent seek key was last used */ /* When the menu was last moved (arrow keys, page up, etc.) */ static time_t last_menu_move_time = (time_t)0; static void sig_quit (int sig LOGIT_ONLY) { log_signal (sig); want_quit = QUIT_CLIENT; } static void sig_interrupt (int sig LOGIT_ONLY) { log_signal (sig); wants_interrupt = 1; } #ifdef SIGWINCH static void sig_winch (int sig LOGIT_ONLY) { log_signal (sig); want_resize = 1; } #endif int user_wants_interrupt () { return wants_interrupt; } static void clear_interrupt () { wants_interrupt = 0; } static void send_int_to_srv (const int num) { if (!send_int(srv_sock, num)) fatal ("Can't send() int to the server!"); } static void send_bool_to_srv (const bool t) { if (!send_int(srv_sock, t ? 1 : 0)) fatal ("Can't send() bool to the server!"); } static void send_str_to_srv (const char *str) { if (!send_str(srv_sock, str)) fatal ("Can't send() string to the server!"); } static void send_item_to_srv (const struct plist_item *item) { if (!send_item(srv_sock, item)) fatal ("Can't send() item to the server!"); } static int get_int_from_srv () { int num; if (!get_int(srv_sock, &num)) fatal ("Can't receive value from the server!"); return num; } static bool get_bool_from_srv () { int num; if (!get_int(srv_sock, &num)) fatal ("Can't receive value from the server!"); return num == 1 ? true : false; } /* Returned memory is malloc()ed. */ static char *get_str_from_srv () { char *str = get_str (srv_sock); if (!str) fatal ("Can't receive string from the server!"); return str; } static struct file_tags *recv_tags_from_srv () { struct file_tags *tags = recv_tags (srv_sock); if (!tags) fatal ("Can't receive tags from the server!"); return tags; } /* Noblocking version of get_int_from_srv(): return 0 if there are no data. */ static int get_int_from_srv_noblock (int *num) { enum noblock_io_status st; if ((st = get_int_noblock(srv_sock, num)) == NB_IO_ERR) fatal ("Can't receive value from the server!"); return st == NB_IO_OK ? 1 : 0; } static struct plist_item *recv_item_from_srv () { struct plist_item *item; if (!(item = recv_item(srv_sock))) fatal ("Can't receive item from the server!"); return item; } static struct tag_ev_response *recv_tags_data_from_srv () { struct tag_ev_response *r; r = (struct tag_ev_response *)xmalloc (sizeof(struct tag_ev_response)); r->file = get_str_from_srv (); if (!(r->tags = recv_tags(srv_sock))) fatal ("Can't receive tags event's data from the server!"); return r; } static struct move_ev_data *recv_move_ev_data_from_srv () { struct move_ev_data *d; if (!(d = recv_move_ev_data(srv_sock))) fatal ("Can't receive move data from the server!"); return d; } /* Receive data for the given type of event and return them. Return NULL if * there is no data for the event. */ static void *get_event_data (const int type) { switch (type) { case EV_PLIST_ADD: case EV_QUEUE_ADD: return recv_item_from_srv (); case EV_PLIST_DEL: case EV_QUEUE_DEL: case EV_STATUS_MSG: case EV_SRV_ERROR: return get_str_from_srv (); case EV_FILE_TAGS: return recv_tags_data_from_srv (); case EV_PLIST_MOVE: case EV_QUEUE_MOVE: return recv_move_ev_data_from_srv (); } return NULL; } /* Wait for EV_DATA handling other events. */ static void wait_for_data () { int event; do { event = get_int_from_srv (); if (event == EV_EXIT) interface_fatal ("The server exited!"); if (event != EV_DATA) event_push (&events, event, get_event_data(event)); } while (event != EV_DATA); } /* Get an integer value from the server that will arrive after EV_DATA. */ static int get_data_int () { wait_for_data (); return get_int_from_srv (); } /* Get a boolean value from the server that will arrive after EV_DATA. */ static bool get_data_bool () { wait_for_data (); return get_bool_from_srv (); } /* Get a string value from the server that will arrive after EV_DATA. */ static char *get_data_str () { wait_for_data (); return get_str_from_srv (); } static struct file_tags *get_data_tags () { wait_for_data (); return recv_tags_from_srv (); } static int send_tags_request (const char *file, const int tags_sel) { assert (file != NULL); assert (tags_sel != 0); if (file_type(file) == F_SOUND) { send_int_to_srv (CMD_GET_FILE_TAGS); send_str_to_srv (file); send_int_to_srv (tags_sel); debug ("Asking for tags for %s", file); return 1; } else { debug ("Not sending tags request for URL (%s)", file); return 0; } } /* Send all items from this playlist to other clients. */ static void send_items_to_clients (const struct plist *plist) { int i; for (i = 0; i < plist->num; i++) if (!plist_deleted(plist, i)) { send_int_to_srv (CMD_CLI_PLIST_ADD); send_item_to_srv (&plist->items[i]); } } static void init_playlists () { dir_plist = (struct plist *)xmalloc (sizeof(struct plist)); plist_init (dir_plist); playlist = (struct plist *)xmalloc (sizeof(struct plist)); plist_init (playlist); queue = (struct plist *)xmalloc (sizeof(struct plist)); plist_init (queue); /* set serial numbers for the playlist */ send_int_to_srv (CMD_GET_SERIAL); plist_set_serial (playlist, get_data_int()); } static void file_info_reset (struct file_info *f) { f->file = NULL; f->tags = NULL; f->title = NULL; f->bitrate = -1; f->rate = -1; f->curr_time = -1; f->total_time = -1; f->channels = 1; f->state = STATE_STOP; } static void file_info_cleanup (struct file_info *f) { if (f->tags) tags_free (f->tags); if (f->file) free (f->file); if (f->title) free (f->title); f->file = NULL; f->tags = NULL; f->title = NULL; } /* Initialise the block marker information. */ static void file_info_block_init (struct file_info *f) { f->block_file = NULL; } /* Reset the block start and end markers. */ static void file_info_block_reset (struct file_info *f) { if (f->block_file) free (f->block_file); f->block_file = NULL; } /* Enter the current time into a block start or end marker. */ static void file_info_block_mark (int *marker) { if (curr_file.state == STATE_STOP) { error ("Cannot make block marks while stopped."); } else if (file_type (curr_file.file) == F_URL) { error ("Cannot make block marks in URLs."); } else if (file_type (curr_file.file) != F_SOUND) { error ("Cannot make block marks in non-audio files."); } else if (!curr_file.block_file) { error ("Cannot make block marks in files of unknown duration."); } else { *marker = curr_file.curr_time; iface_set_block (curr_file.block_start, curr_file.block_end); } } /* Get a boolean option from the server (like Shuffle) and set it. */ static void sync_bool_option (const char *name) { bool value; send_int_to_srv (CMD_GET_OPTION); send_str_to_srv (name); value = get_data_bool (); options_set_bool (name, value); iface_set_option_state (name, value); } /* Get the server options and set our options like them. */ static void get_server_options () { sync_bool_option ("Shuffle"); sync_bool_option ("Repeat"); sync_bool_option ("AutoNext"); } static int get_server_plist_serial () { send_int_to_srv (CMD_PLIST_GET_SERIAL); return get_data_int (); } static int get_mixer_value () { send_int_to_srv (CMD_GET_MIXER); return get_data_int (); } static int get_state () { send_int_to_srv (CMD_GET_STATE); return get_data_int (); } static int get_channels () { send_int_to_srv (CMD_GET_CHANNELS); return get_data_int (); } static int get_rate () { send_int_to_srv (CMD_GET_RATE); return get_data_int (); } static int get_bitrate () { send_int_to_srv (CMD_GET_BITRATE); return get_data_int (); } static int get_avg_bitrate () { send_int_to_srv (CMD_GET_AVG_BITRATE); return get_data_int (); } static int get_curr_time () { send_int_to_srv (CMD_GET_CTIME); return get_data_int (); } static char *get_curr_file () { send_int_to_srv (CMD_GET_SNAME); return get_data_str (); } static void update_mixer_value () { int val; val = get_mixer_value (); iface_set_mixer_value (MAX(val, 0)); } static void update_mixer_name () { char *name; send_int_to_srv (CMD_GET_MIXER_CHANNEL_NAME); name = get_data_str(); debug ("Mixer name: %s", name); iface_set_mixer_name (name); free (name); update_mixer_value (); } /* Make new cwd path from CWD and this path. */ static void set_cwd (const char *path) { if (path[0] == '/') strcpy (cwd, "/"); /* for absolute path */ else if (!cwd[0]) { if (!getcwd(cwd, sizeof(cwd))) fatal ("Can't get CWD: %s", xstrerror (errno)); } resolve_path (cwd, sizeof(cwd), path); } /* Try to find the directory we can start and set cwd to it. */ static void set_start_dir () { if (!getcwd(cwd, sizeof (cwd))) { if (errno == ERANGE) fatal ("CWD is larger than PATH_MAX!"); strncpy (cwd, get_home (), sizeof (cwd)); if (cwd[sizeof (cwd) - 1]) fatal ("Home directory path is longer than PATH_MAX!"); } } /* Set cwd to last directory written to a file, return 1 on success. */ static int read_last_dir () { FILE *dir_file; int res = 1; int read; if (!(dir_file = fopen(create_file_name("last_directory"), "r"))) return 0; if ((read = fread(cwd, sizeof(char), sizeof(cwd)-1, dir_file)) == 0) res = 0; else cwd[read] = 0; fclose (dir_file); return res; } /* Check if dir2 is in dir1. */ static int is_subdir (const char *dir1, const char *dir2) { return !strncmp(dir1, dir2, strlen(dir1)) ? 1 : 0; } static int sort_strcmp_func (const void *a, const void *b) { return strcoll (*(char **)a, *(char **)b); } static int sort_dirs_func (const void *a, const void *b) { char *sa = *(char **)a; char *sb = *(char **)b; /* '../' is always first */ if (!strcmp(sa, "../")) return -1; if (!strcmp(sb, "../")) return 1; return strcoll (sa, sb); } static int get_tags_setting () { int needed_tags = 0; if (options_get_bool("ReadTags")) needed_tags |= TAGS_COMMENTS; if (!strcasecmp(options_get_symb("ShowTime"), "yes")) needed_tags |= TAGS_TIME; return needed_tags; } /* For each file in the playlist, send a request for all the given tags if * the file is missing any of those tags. Return the number of requests. */ static int ask_for_tags (const struct plist *plist, const int tags_sel) { int i; int req = 0; assert (plist != NULL); if (tags_sel != 0) { for (i = 0; i < plist->num; i++) { if (!plist_deleted(plist, i) && (!plist->items[i].tags || ~plist->items[i].tags->filled & tags_sel)) { char *file; file = plist_get_file (plist, i); req += send_tags_request (file, tags_sel); free (file); } } } return req; } static void interface_message (const char *format, ...) { va_list va; char *msg; va_start (va, format); msg = format_msg_va (format, va); va_end (va); iface_message (msg); free (msg); } /* Update tags (and titles) for the given item on the playlist with new tags. */ static void update_item_tags (struct plist *plist, const int num, const struct file_tags *tags) { struct file_tags *old_tags = plist_get_tags (plist, num); plist_set_tags (plist, num, tags); /* Get the time from the old tags if it's not present in the new tags. * FIXME: There is risk, that the file was modified and the time * from the old tags is not valid. */ if (!(tags->filled & TAGS_TIME) && old_tags && old_tags->time != -1) plist_set_item_time (plist, num, old_tags->time); if (plist->items[num].title_tags) { free (plist->items[num].title_tags); plist->items[num].title_tags = NULL; } make_tags_title (plist, num); if (options_get_bool ("ReadTags") && !plist->items[num].title_tags) { if (!plist->items[num].title_file) make_file_title (plist, num, options_get_bool ("HideFileExtension")); } if (old_tags) tags_free (old_tags); } /* Truncate string at screen-upsetting whitespace. */ static void sanitise_string (char *str) { if (!str) return; while (*str) { if (*str != ' ' && isspace (*str)) { *str = 0x00; break; } str++; } } /* Handle EV_FILE_TAGS. */ static void ev_file_tags (const struct tag_ev_response *data) { int n; assert (data != NULL); assert (data->file != NULL); assert (data->tags != NULL); debug ("Received tags for %s", data->file); sanitise_string (data->tags->title); sanitise_string (data->tags->artist); sanitise_string (data->tags->album); if ((n = plist_find_fname(dir_plist, data->file)) != -1) { update_item_tags (dir_plist, n, data->tags); iface_update_item (IFACE_MENU_DIR, dir_plist, n); } if ((n = plist_find_fname(playlist, data->file)) != -1) { update_item_tags (playlist, n, data->tags); iface_update_item (IFACE_MENU_PLIST, playlist, n); } if (curr_file.file && !strcmp(data->file, curr_file.file)) { debug ("Tags apply to the currently played file."); if (data->tags->time != -1) { curr_file.total_time = data->tags->time; iface_set_total_time (curr_file.total_time); if (file_type (curr_file.file) == F_SOUND) { if (!curr_file.block_file) { curr_file.block_file = xstrdup(curr_file.file); curr_file.block_start = 0; curr_file.block_end = curr_file.total_time; } iface_set_block (curr_file.block_start, curr_file.block_end); } } else debug ("No time information"); if (data->tags->title) { if (curr_file.title) free (curr_file.title); curr_file.title = build_title (data->tags); iface_set_played_file_title (curr_file.title); } if (curr_file.tags) tags_free (curr_file.tags); curr_file.tags = tags_dup (data->tags); } } /* Update the current time. */ static void update_ctime () { curr_file.curr_time = get_curr_time (); if (silent_seek_pos == -1) iface_set_curr_time (curr_file.curr_time); } /* Use new tags for current file title (for Internet streams). */ static void update_curr_tags () { if (curr_file.file && is_url(curr_file.file)) { if (curr_file.tags) tags_free (curr_file.tags); send_int_to_srv (CMD_GET_TAGS); curr_file.tags = get_data_tags (); if (curr_file.tags->title) { if (curr_file.title) free (curr_file.title); curr_file.title = build_title (curr_file.tags); iface_set_played_file_title (curr_file.title); } } } /* Make sure that the currently played file is visible if it is in one of our * menus. */ static void follow_curr_file () { if (curr_file.file && file_type(curr_file.file) == F_SOUND && last_menu_move_time <= time(NULL) - 2) { int server_plist_serial = get_server_plist_serial(); if (server_plist_serial == plist_get_serial(playlist)) iface_make_visible (IFACE_MENU_PLIST, curr_file.file); else if (server_plist_serial == plist_get_serial(dir_plist)) iface_make_visible (IFACE_MENU_DIR, curr_file.file); else logit ("Not my playlist."); } } static void update_curr_file () { char *file; file = get_curr_file (); if (!file[0] || curr_file.state == STATE_STOP) { /* Nothing is played/paused. */ file_info_cleanup (&curr_file); file_info_reset (&curr_file); iface_set_played_file (NULL); iface_load_lyrics (NULL); free (file); } else if (file[0] && (!curr_file.file || strcmp(file, curr_file.file))) { /* played file has changed */ file_info_cleanup (&curr_file); if (curr_file.block_file && strcmp(file, curr_file.block_file)) file_info_block_reset (&curr_file); /* The total time could not get reset. */ iface_set_total_time (-1); iface_set_played_file (file); send_tags_request (file, TAGS_COMMENTS | TAGS_TIME); curr_file.file = file; /* make a title that will be used until we get tags */ if (file_type(file) == F_URL || !strchr(file, '/')) { curr_file.title = xstrdup (file); update_curr_tags (); } else { if (options_get_bool ("FileNamesIconv")) { curr_file.title = files_iconv_str ( strrchr(file, '/') + 1); } else { curr_file.title = xstrdup ( strrchr(file, '/') + 1); } } iface_set_played_file (file); iface_set_played_file_title (curr_file.title); /* Try to load the lyrics of the new file. */ iface_load_lyrics (file); /* Silent seeking makes no sense if the playing file has changed. */ silent_seek_pos = -1; iface_set_curr_time (curr_file.curr_time); if (options_get_bool("FollowPlayedFile")) follow_curr_file (); } else free (file); } static void update_rate () { curr_file.rate = get_rate (); iface_set_rate (curr_file.rate); } static void update_channels () { curr_file.channels = get_channels () == 2 ? 2 : 1; iface_set_channels (curr_file.channels); } static void update_bitrate () { curr_file.bitrate = get_bitrate (); iface_set_bitrate (curr_file.bitrate); } /* Get and show the server state. */ static void update_state () { int old_state = curr_file.state; /* play | stop | pause */ curr_file.state = get_state (); iface_set_state (curr_file.state); /* Silent seeking makes no sense if the state has changed. */ if (old_state != curr_file.state) silent_seek_pos = -1; update_curr_file (); update_channels (); update_bitrate (); update_rate (); update_ctime (); } /* Handle EV_PLIST_ADD. */ static void event_plist_add (const struct plist_item *item) { if (plist_find_fname(playlist, item->file) == -1) { int item_num = plist_add_from_item (playlist, item); int needed_tags = 0; int i; if (options_get_bool("ReadTags") && (!item->tags || !item->tags->title)) needed_tags |= TAGS_COMMENTS; if (!strcasecmp(options_get_symb("ShowTime"), "yes") && (!item->tags || item->tags->time == -1)) needed_tags |= TAGS_TIME; if (needed_tags) send_tags_request (item->file, needed_tags); if (options_get_bool ("ReadTags")) make_tags_title (playlist, item_num); else make_file_title (playlist, item_num, options_get_bool ("HideFileExtension")); /* Just calling iface_update_queue_positions (queue, playlist, * NULL, NULL) is too slow in cases when we receive a large * number of items from server (e.g., loading playlist w/ * SyncPlaylist on). Since we know the filename in question, * we try to find it in queue and eventually update the value. */ if ((i = plist_find_fname(queue, item->file)) != -1) { playlist->items[item_num].queue_pos = plist_get_position (queue, i); } iface_add_to_plist (playlist, item_num); if (waiting_for_plist_load) { if (iface_in_dir_menu()) iface_switch_to_plist (); waiting_for_plist_load = 0; } } } /* Handle EV_QUEUE_ADD. */ static void event_queue_add (const struct plist_item *item) { if (plist_find_fname(queue, item->file) == -1) { plist_add_from_item (queue, item); iface_set_files_in_queue (plist_count(queue)); iface_update_queue_position_last (queue, playlist, dir_plist); logit ("Adding %s to queue", item->file); } else logit ("Adding file already present in queue"); } /* Get error message from the server and show it. */ static void update_error (char *err) { error ("%s", err); } /* Send the playlist to the server to be forwarded to another client. */ static void forward_playlist () { int i; debug ("Forwarding the playlist..."); send_int_to_srv (CMD_SEND_PLIST); send_int_to_srv (plist_get_serial(playlist)); for (i = 0; i < playlist->num; i++) if (!plist_deleted(playlist, i)) send_item_to_srv (&playlist->items[i]); send_item_to_srv (NULL); } static int recv_server_plist (struct plist *plist) { int end_of_list = 0; struct plist_item *item; logit ("Asking server for the playlist from other client."); send_int_to_srv (CMD_GET_PLIST); logit ("Waiting for response"); wait_for_data (); if (!get_int_from_srv()) { debug ("There is no playlist"); return 0; /* there are no other clients with a playlist */ } logit ("There is a playlist, getting..."); wait_for_data (); logit ("Transfer..."); plist_set_serial (plist, get_int_from_srv()); do { item = recv_item_from_srv (); if (item->file[0]) plist_add_from_item (plist, item); else end_of_list = 1; plist_free_item_fields (item); free (item); } while (!end_of_list); return 1; } static void recv_server_queue (struct plist *queue) { int end_of_list = 0; struct plist_item *item; logit ("Asking server for the queue."); send_int_to_srv (CMD_GET_QUEUE); logit ("Waiting for response"); wait_for_data (); /* There must always be (possibly empty) queue. */ do { item = recv_item_from_srv (); if (item->file[0]) plist_add_from_item (queue, item); else end_of_list = 1; plist_free_item_fields (item); free (item); } while (!end_of_list); } /* Clear the playlist locally. */ static void clear_playlist () { if (iface_in_plist_menu()) iface_switch_to_dir (); plist_clear (playlist); iface_clear_plist (); if (!waiting_for_plist_load) interface_message ("The playlist was cleared."); iface_set_status (""); } static void clear_queue () { iface_clear_queue_positions (queue, playlist, dir_plist); plist_clear (queue); iface_set_files_in_queue (0); interface_message ("The queue was cleared."); } /* Handle EV_PLIST_DEL. */ static void event_plist_del (char *file) { int item = plist_find_fname (playlist, file); if (item != -1) { char *file; int have_all_times; int playlist_total_time; file = plist_get_file (playlist, item); plist_delete (playlist, item); iface_del_plist_item (file); playlist_total_time = plist_total_time (playlist, &have_all_times); iface_plist_set_total_time (playlist_total_time, have_all_times); free (file); if (plist_count(playlist) == 0) clear_playlist (); } else logit ("Server requested deleting an item not present on the" " playlist."); } /* Handle EV_QUEUE_DEL. */ static void event_queue_del (char *file) { int item = plist_find_fname (queue, file); if (item != -1) { plist_delete (queue, item); /* Free the deleted items occasionally. * QUEUE_CLEAR_THRESH is chosen to be two times * the initial size of the playlist. */ if (plist_count(queue) == 0 && queue->num >= QUEUE_CLEAR_THRESH) plist_clear (queue); iface_set_files_in_queue (plist_count(queue)); iface_update_queue_positions (queue, playlist, dir_plist, file); logit ("Deleting %s from queue", file); } else logit ("Deleting an item not present in the queue"); } /* Swap 2 file on the playlist. */ static void swap_playlist_items (const char *file1, const char *file2) { assert (file1 != NULL); assert (file2 != NULL); plist_swap_files (playlist, file1, file2); iface_swap_plist_items (file1, file2); } /* Handle EV_PLIST_MOVE. */ static void event_plist_move (const struct move_ev_data *d) { assert (d != NULL); assert (d->from != NULL); assert (d->to != NULL); swap_playlist_items (d->from, d->to); } /* Handle EV_QUEUE_MOVE. */ static void event_queue_move (const struct move_ev_data *d) { assert (d != NULL); assert (d->from != NULL); assert (d->to != NULL); plist_swap_files (queue, d->from, d->to); } /* Handle server event. */ static void server_event (const int event, void *data) { logit ("EVENT: 0x%02x", event); switch (event) { case EV_BUSY: interface_fatal ("The server is busy; " "too many other clients are connected!"); break; case EV_CTIME: update_ctime (); break; case EV_STATE: update_state (); break; case EV_EXIT: interface_fatal ("The server exited!"); break; case EV_BITRATE: update_bitrate (); break; case EV_RATE: update_rate (); break; case EV_CHANNELS: update_channels (); break; case EV_SRV_ERROR: update_error ((char *)data); break; case EV_OPTIONS: get_server_options (); break; case EV_SEND_PLIST: forward_playlist (); break; case EV_PLIST_ADD: if (options_get_bool("SyncPlaylist")) event_plist_add ((struct plist_item *)data); break; case EV_PLIST_CLEAR: if (options_get_bool("SyncPlaylist")) clear_playlist (); break; case EV_PLIST_DEL: if (options_get_bool("SyncPlaylist")) event_plist_del ((char *)data); break; case EV_PLIST_MOVE: if (options_get_bool("SyncPlaylist")) event_plist_move ((struct move_ev_data *)data); break; case EV_TAGS: update_curr_tags (); break; case EV_STATUS_MSG: iface_set_status ((char *)data); break; case EV_MIXER_CHANGE: update_mixer_name (); break; case EV_FILE_TAGS: ev_file_tags ((struct tag_ev_response *)data); break; case EV_AVG_BITRATE: curr_file.avg_bitrate = get_avg_bitrate (); break; case EV_QUEUE_ADD: event_queue_add ((struct plist_item *)data); break; case EV_QUEUE_DEL: event_queue_del ((char *)data); break; case EV_QUEUE_CLEAR: clear_queue (); break; case EV_QUEUE_MOVE: event_queue_move ((struct move_ev_data *)data); break; case EV_AUDIO_START: break; case EV_AUDIO_STOP: break; default: interface_fatal ("Unknown event: 0x%02x!", event); } free_event_data (event, data); } /* Send requests for the given tags for every file on the playlist and wait * for all responses. If no_iface has non-zero value, it will not access the * interface. */ static void fill_tags (struct plist *plist, const int tags_sel, const int no_iface) { int files; assert (plist != NULL); assert (tags_sel != 0); iface_set_status ("Reading tags..."); files = ask_for_tags (plist, tags_sel); /* Process events until we have all tags. */ while (files && !user_wants_interrupt()) { int type; void *data; /* Event queue is not initialized if there is no interface. */ if (!no_iface && !event_queue_empty (&events)) { struct event e = *event_get_first (&events); type = e.type; data = e.data; event_pop (&events); } else { type = get_int_from_srv (); data = get_event_data (type); } if (type == EV_FILE_TAGS) { struct tag_ev_response *ev = (struct tag_ev_response *)data; int n; if ((n = plist_find_fname(plist, ev->file)) != -1) { if ((ev->tags->filled & tags_sel)) files--; update_item_tags (plist, n, ev->tags); } } else if (no_iface) abort (); /* can't handle other events without the interface */ if (!no_iface) server_event (type, data); else free_event_data (type, data); } iface_set_status (""); } /* Load the directory content into dir_plist and switch the menu to it. * If dir is NULL, go to the cwd. If reload is not zero, we are reloading * the current directory, so use iface_update_dir_content(). * Return 1 on success, 0 on error. */ static int go_to_dir (const char *dir, const int reload) { struct plist *old_dir_plist; char last_dir[PATH_MAX]; const char *new_dir = dir ? dir : cwd; int going_up = 0; lists_t_strs *dirs, *playlists; iface_set_status ("Reading directory..."); if (dir && is_subdir(dir, cwd)) { strcpy (last_dir, strrchr(cwd, '/') + 1); strcat (last_dir, "/"); going_up = 1; } old_dir_plist = dir_plist; dir_plist = (struct plist *)xmalloc (sizeof(struct plist)); plist_init (dir_plist); dirs = lists_strs_new (FILES_LIST_INIT_SIZE); playlists = lists_strs_new (FILES_LIST_INIT_SIZE); if (!read_directory(new_dir, dirs, playlists, dir_plist)) { iface_set_status (""); plist_free (dir_plist); lists_strs_free (dirs); lists_strs_free (playlists); free (dir_plist); dir_plist = old_dir_plist; return 0; } /* TODO: use CMD_ABORT_TAGS_REQUESTS (what if we requested tags for the playlist?) */ plist_free (old_dir_plist); free (old_dir_plist); if (dir) /* if dir is NULL, we went to cwd */ strcpy (cwd, dir); switch_titles_file (dir_plist); plist_sort_fname (dir_plist); lists_strs_sort (dirs, sort_dirs_func); lists_strs_sort (playlists, sort_strcmp_func); ask_for_tags (dir_plist, get_tags_setting()); if (reload) iface_update_dir_content (IFACE_MENU_DIR, dir_plist, dirs, playlists); else iface_set_dir_content (IFACE_MENU_DIR, dir_plist, dirs, playlists); lists_strs_free (dirs); lists_strs_free (playlists); if (going_up) iface_set_curr_item_title (last_dir); iface_set_title (IFACE_MENU_DIR, cwd); iface_update_queue_positions (queue, NULL, dir_plist, NULL); if (iface_in_plist_menu()) iface_switch_to_dir (); return 1; } /* Make sure that the server's playlist has different serial from ours. */ static void change_srv_plist_serial () { int serial; do { send_int_to_srv (CMD_GET_SERIAL); serial = get_data_int (); } while (serial == plist_get_serial(playlist) || serial == plist_get_serial(dir_plist)); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (serial); } static void enter_first_dir (); /* Switch between the directory view and the playlist. */ static void toggle_menu () { int num; if (iface_in_plist_menu()) { if (!cwd[0]) /* we were at the playlist from the startup */ enter_first_dir (); else iface_switch_to_dir (); } else if ((num = plist_count(playlist))) iface_switch_to_plist (); else error ("The playlist is empty."); } /* Load the playlist file and switch the menu to it. Return 1 on success. */ static int go_to_playlist (const char *file, const int load_serial, bool default_playlist) { if (plist_count(playlist)) { error ("Please clear the playlist, because " "I'm not sure you want to do this."); return 0; } plist_clear (playlist); iface_set_status ("Loading playlist..."); if (plist_load(playlist, file, cwd, load_serial)) { if (options_get_bool("SyncPlaylist")) { send_int_to_srv (CMD_LOCK); if (!load_serial) change_srv_plist_serial (); send_int_to_srv (CMD_CLI_PLIST_CLEAR); iface_set_status ("Notifying clients..."); send_items_to_clients (playlist); iface_set_status (""); waiting_for_plist_load = 1; send_int_to_srv (CMD_UNLOCK); /* We'll use the playlist received from the * server to be synchronized with other clients. */ plist_clear (playlist); } else { if (!default_playlist) toggle_menu (); iface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL); iface_update_queue_positions (queue, playlist, NULL, NULL); } interface_message ("Playlist loaded."); } else { interface_message ("The playlist is empty"); iface_set_status (""); return 0; } return 1; } /* Enter to the initial directory or toggle to the initial playlist (only * if the function has not been called yet). */ static void enter_first_dir () { static int first_run = 1; if (options_get_bool("StartInMusicDir")) { char *music_dir; if ((music_dir = options_get_str("MusicDir"))) { set_cwd (music_dir); if (first_run && file_type(music_dir) == F_PLAYLIST && plist_count(playlist) == 0 && go_to_playlist(music_dir, 0, false)) { cwd[0] = 0; first_run = 0; } else if (file_type(cwd) == F_DIR && go_to_dir(NULL, 0)) { first_run = 0; return; } } else error ("MusicDir is not set"); } if (!(read_last_dir() && go_to_dir(NULL, 0))) { set_start_dir (); if (!go_to_dir(NULL, 0)) interface_fatal ("Can't enter any directory!"); } first_run = 0; } /* Request the playlist from the server (given by another client). Make * the titles. Return 0 if such a list doesn't exist. */ static int get_server_playlist (struct plist *plist) { iface_set_status ("Getting the playlist..."); debug ("Getting the playlist..."); if (recv_server_plist(plist)) { ask_for_tags (plist, get_tags_setting()); if (options_get_bool ("ReadTags")) switch_titles_tags (plist); else switch_titles_file (plist); iface_set_status (""); return 1; } iface_set_status (""); return 0; } /* Get the playlist from another client and use it as our playlist. * Return 0 if there is no client with a playlist. */ static int use_server_playlist () { if (get_server_playlist(playlist)) { iface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL); iface_update_queue_positions (queue, playlist, NULL, NULL); return 1; } return 0; } static void use_server_queue () { iface_set_status ("Getting the queue..."); debug ("Getting the queue..."); recv_server_queue(queue); iface_set_files_in_queue (plist_count(queue)); iface_update_queue_positions (queue, playlist, dir_plist, NULL); iface_set_status (""); } /* Process a single directory argument. */ static void process_dir_arg (const char *dir) { set_cwd (dir); if (!go_to_dir (NULL, 0)) enter_first_dir (); } /* Process a single playlist argument. */ static void process_plist_arg (const char *file) { char path[PATH_MAX + 1]; /* the playlist's directory */ char *slash; if (file[0] == '/') strcpy (path, "/"); else if (!getcwd (path, sizeof (path))) interface_fatal ("Can't get CWD: %s", xstrerror (errno)); resolve_path (path, sizeof (path), file); slash = strrchr (path, '/'); assert (slash != NULL); *slash = 0; iface_set_status ("Loading playlist..."); plist_load (playlist, file, path, 0); iface_set_status (""); } /* Process a list of arguments. */ static void process_multiple_args (lists_t_strs *args) { int size, ix; const char *arg; char this_cwd[PATH_MAX]; if (!getcwd (this_cwd, sizeof (cwd))) interface_fatal ("Can't get CWD: %s", xstrerror (errno)); size = lists_strs_size (args); for (ix = 0; ix < size; ix += 1) { int dir; char path[2 * PATH_MAX]; arg = lists_strs_at (args, ix); dir = is_dir (arg); if (is_url (arg)) { strncpy (path, arg, sizeof (path)); path[sizeof(path) - 1] = 0; } else { if (arg[0] == '/') strcpy (path, "/"); else strcpy (path, this_cwd); resolve_path (path, sizeof (path), arg); } if (dir == 1) read_directory_recurr (path, playlist); else if (!dir && (is_sound_file (path) || is_url (path))) { if (plist_find_fname (playlist, path) == -1) plist_add (playlist, path); } else if (is_plist_file (path)) { char *plist_dir, *slash; /* Here we've chosen to resolve the playlist's relative paths * with respect to the directory of the playlist (or of the * symlink being used to reference it). If some other base is * desired, then we probably need to introduce a new option. */ plist_dir = xstrdup (path); slash = strrchr (plist_dir, '/'); assert (slash != NULL); *slash = 0; plist_load (playlist, path, plist_dir, 0); free (plist_dir); } } } /* Process file names passed as arguments. */ static void process_args (lists_t_strs *args) { int size; const char *arg; size = lists_strs_size (args); arg = lists_strs_at (args, 0); if (size == 1 && is_dir (arg) == 1) { process_dir_arg (arg); return; } if (size == 1 && is_plist_file (arg)) process_plist_arg (arg); else process_multiple_args (args); if (plist_count (playlist) && !options_get_bool ("SyncPlaylist")) { switch_titles_file (playlist); ask_for_tags (playlist, get_tags_setting ()); iface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL); iface_update_queue_positions (queue, playlist, NULL, NULL); iface_switch_to_plist (); } else enter_first_dir (); } /* Load the playlist from .moc directory. */ static void load_playlist () { char *plist_file = create_file_name (PLAYLIST_FILE); if (file_type(plist_file) == F_PLAYLIST) { go_to_playlist (plist_file, 1, true); /* We don't want to switch to the playlist after loading. */ waiting_for_plist_load = 0; } } #ifdef SIGWINCH /* Handle resizing xterm. */ static void do_resize () { iface_resize (); logit ("resize"); want_resize = 0; } #endif /* Strip the last directory from the path. Returned memory is mallod()ed. */ static char *dir_up (const char *path) { char *slash; char *dir; assert (path != NULL); dir = xstrdup (path); slash = strrchr (dir, '/'); assert (slash != NULL); if (slash == dir) *(slash + 1) = 0; else *slash = 0; return dir; } static void go_dir_up () { char *dir; dir = dir_up (cwd); go_to_dir (dir, 0); free (dir); } /* Return a generated playlist serial from the server and make sure * it's not the same as our playlist's serial. */ static int get_safe_serial () { int serial; do { send_int_to_srv (CMD_GET_SERIAL); serial = get_data_int (); } while (playlist && serial == plist_get_serial(playlist)); /* check only the playlist, because dir_plist has serial -1 */ return serial; } /* Send the playlist to the server. If clear != 0, clear the server's playlist * before sending. */ static void send_playlist (struct plist *plist, const int clear) { int i; if (clear) send_int_to_srv (CMD_LIST_CLEAR); for (i = 0; i < plist->num; i++) { if (!plist_deleted(plist, i)) { send_int_to_srv (CMD_LIST_ADD); send_str_to_srv (plist->items[i].file); } } } /* Send the playlist to the server if necessary and request playing this * item. */ static void play_it (const char *file) { struct plist *curr_plist; assert (file != NULL); if (iface_in_dir_menu()) curr_plist = dir_plist; else curr_plist = playlist; send_int_to_srv (CMD_LOCK); if (plist_get_serial(curr_plist) == -1 || get_server_plist_serial() != plist_get_serial(curr_plist)) { int serial; logit ("The server has different playlist"); serial = get_safe_serial(); plist_set_serial (curr_plist, serial); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (serial); send_playlist (curr_plist, 1); } else logit ("The server already has my playlist"); send_int_to_srv (CMD_PLAY); send_str_to_srv (file); send_int_to_srv (CMD_UNLOCK); } /* Action when the user selected a file. */ static void go_file () { enum file_type type = iface_curritem_get_type (); char *file = iface_get_curr_file (); if (!file) return; if (type == F_SOUND || type == F_URL) play_it (file); else if (type == F_DIR && iface_in_dir_menu()) { if (!strcmp(file, "..")) go_dir_up (); else go_to_dir (file, 0); } else if (type == F_PLAYLIST) go_to_playlist (file, 0, false); free (file); } /* pause/unpause */ static void switch_pause () { switch (curr_file.state) { case STATE_PLAY: send_int_to_srv (CMD_PAUSE); break; case STATE_PAUSE: send_int_to_srv (CMD_UNPAUSE); break; default: logit ("User pressed pause when not playing."); } } static void set_mixer (int val) { val = CLAMP(0, val, 100); send_int_to_srv (CMD_SET_MIXER); send_int_to_srv (val); } static void adjust_mixer (const int diff) { set_mixer (get_mixer_value() + diff); } /* Recursively add the content of a directory to the playlist. */ static void add_dir_plist () { struct plist plist; char *file; enum file_type type; if (iface_in_plist_menu()) { error ("Can't add to the playlist a file from the playlist."); return; } file = iface_get_curr_file (); if (!file) return; type = iface_curritem_get_type (); if (type != F_DIR && type != F_PLAYLIST) { error ("This is not a directory or a playlist."); free (file); return; } if (!strcmp(file, "..")) { error ("Can't add '..'."); free (file); return; } iface_set_status ("Reading directories..."); plist_init (&plist); if (type == F_DIR) { read_directory_recurr (file, &plist); plist_sort_fname (&plist); } else plist_load (&plist, file, cwd, 0); send_int_to_srv (CMD_LOCK); plist_remove_common_items (&plist, playlist); /* Add the new files to the server's playlist if the server has our * playlist. */ if (get_server_plist_serial() == plist_get_serial(playlist)) send_playlist (&plist, 0); if (options_get_bool("SyncPlaylist")) { iface_set_status ("Notifying clients..."); send_items_to_clients (&plist); iface_set_status (""); } else { int i; switch_titles_file (&plist); ask_for_tags (&plist, get_tags_setting()); for (i = 0; i < plist.num; i++) if (!plist_deleted(&plist, i)) iface_add_to_plist (&plist, i); plist_cat (playlist, &plist); } send_int_to_srv (CMD_UNLOCK); plist_free (&plist); free (file); } /* To avoid lots of locks and unlocks, this assumes a lock is sent before * the first call and an unlock after the last. * * It's also assumed to be in the menu. */ static void remove_file_from_playlist (const char *file) { assert (file != NULL); assert (plist_count(playlist) > 0); if (options_get_bool("SyncPlaylist")) { send_int_to_srv (CMD_CLI_PLIST_DEL); send_str_to_srv (file); } else { int n = plist_find_fname (playlist, file); assert (n != -1); plist_delete (playlist, n); iface_del_plist_item (file); if (plist_count(playlist) == 0) clear_playlist (); } /* Delete this item from the server's playlist if it has our * playlist. */ if (get_server_plist_serial() == plist_get_serial(playlist)) { send_int_to_srv (CMD_DELETE); send_str_to_srv (file); } } /* Remove all dead entries (point to non-existent or unreadable). */ static void remove_dead_entries_plist () { const char *file = NULL; int i; if (! iface_in_plist_menu()) { error ("Can't prune when not in the playlist."); return; } send_int_to_srv (CMD_LOCK); for (i = 0, file = plist_get_next_dead_entry(playlist, &i); file != NULL; file = plist_get_next_dead_entry(playlist, &i)) { remove_file_from_playlist (file); } send_int_to_srv (CMD_UNLOCK); } /* Add the currently selected file to the playlist. */ static void add_file_plist () { char *file; if (iface_in_plist_menu()) { error ("Can't add to the playlist a file from the playlist."); return; } if (iface_curritem_get_type() == F_DIR) { add_dir_plist(); return; } file = iface_get_curr_file (); if (!file) return; if (iface_curritem_get_type() != F_SOUND) { error ("You can only add a file using this command."); free (file); return; } if (plist_find_fname(playlist, file) == -1) { struct plist_item *item = &dir_plist->items[ plist_find_fname(dir_plist, file)]; send_int_to_srv (CMD_LOCK); if (options_get_bool("SyncPlaylist")) { send_int_to_srv (CMD_CLI_PLIST_ADD); send_item_to_srv (item); } else { int added; added = plist_add_from_item (playlist, item); iface_add_to_plist (playlist, added); } /* Add to the server's playlist if the server has our * playlist. */ if (get_server_plist_serial() == plist_get_serial(playlist)) { send_int_to_srv (CMD_LIST_ADD); send_str_to_srv (file); } send_int_to_srv (CMD_UNLOCK); } else error ("The file is already on the playlist."); iface_menu_key (KEY_CMD_MENU_DOWN); free (file); } static void queue_toggle_file () { char *file; file = iface_get_curr_file (); if (!file) return; if (iface_curritem_get_type() != F_SOUND && iface_curritem_get_type() != F_URL) { error ("You can only add a file or URL using this command."); free (file); return; } /* Check if the file is already in the queue; if it isn't, add it, * otherwise, remove it. */ if (plist_find_fname(queue, file) == -1) { /* Add item to the server's queue. */ send_int_to_srv (CMD_QUEUE_ADD); send_str_to_srv (file); logit ("Added to queue: %s", file); } else { /* Delete this item from the server's queue. */ send_int_to_srv (CMD_QUEUE_DEL); send_str_to_srv (file); logit ("Removed from queue: %s", file); } iface_menu_key (KEY_CMD_MENU_DOWN); free (file); } static void toggle_option (const char *name) { send_int_to_srv (CMD_SET_OPTION); send_str_to_srv (name); send_bool_to_srv (!options_get_bool(name)); sync_bool_option (name); } static void toggle_show_time () { if (!strcasecmp (options_get_symb ("ShowTime"), "yes")) { options_set_symb ("ShowTime", "IfAvailable"); iface_set_status ("ShowTime: IfAvailable"); } else if (!strcasecmp (options_get_symb ("ShowTime"), "no")) { options_set_symb ("ShowTime", "yes"); iface_update_show_time (); ask_for_tags (dir_plist, TAGS_TIME); ask_for_tags (playlist, TAGS_TIME); iface_set_status ("ShowTime: yes"); } else { /* IfAvailable */ options_set_symb ("ShowTime", "no"); iface_update_show_time (); iface_set_status ("ShowTime: no"); } } static void toggle_show_format () { bool show_format = !options_get_bool("ShowFormat"); options_set_bool ("ShowFormat", show_format); if (show_format) iface_set_status ("ShowFormat: yes"); else iface_set_status ("ShowFormat: no"); iface_update_show_format (); } /* Reread the directory. */ static void reread_dir () { go_to_dir (NULL, 1); } /* Clear the playlist on user request. */ static void cmd_clear_playlist () { if (options_get_bool("SyncPlaylist")) { send_int_to_srv (CMD_LOCK); send_int_to_srv (CMD_CLI_PLIST_CLEAR); change_srv_plist_serial (); send_int_to_srv (CMD_UNLOCK); } else clear_playlist (); } static void cmd_clear_queue () { send_int_to_srv (CMD_QUEUE_CLEAR); } static void go_to_music_dir () { const char *musicdir_optn; char music_dir[PATH_MAX] = "/"; musicdir_optn = options_get_str ("MusicDir"); if (!musicdir_optn) { error ("MusicDir not defined"); return; } resolve_path (music_dir, sizeof(music_dir), musicdir_optn); switch (file_type (music_dir)) { case F_DIR: go_to_dir (music_dir, 0); break; case F_PLAYLIST: go_to_playlist (music_dir, 0, false); break; default: error ("MusicDir is neither a directory nor a playlist!"); } } /* Make a directory from the string resolving ~, './' and '..'. * Return the directory, the memory is malloc()ed. * Return NULL on error. */ static char *make_dir (const char *str) { char *dir; int add_slash = 0; dir = (char *)xmalloc (sizeof(char) * (PATH_MAX + 1)); dir[PATH_MAX] = 0; /* If the string ends with a slash and is not just '/', add this * slash. */ if (strlen(str) > 1 && str[strlen(str)-1] == '/') add_slash = 1; if (str[0] == '~') { strncpy (dir, get_home (), PATH_MAX); if (dir[PATH_MAX]) { logit ("Path too long!"); return NULL; } if (!strcmp(str, "~")) add_slash = 1; str++; } else if (str[0] != '/') strcpy (dir, cwd); else strcpy (dir, "/"); resolve_path (dir, PATH_MAX, str); if (add_slash && strlen(dir) < PATH_MAX) strcat (dir, "/"); return dir; } static void entry_key_go_dir (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\t') { char *dir; char *complete_dir; char buf[PATH_MAX+1]; char *entry_text; entry_text = iface_entry_get_text (); if (!(dir = make_dir(entry_text))) { free (entry_text); return; } free (entry_text); complete_dir = find_match_dir (dir); strncpy (buf, complete_dir ? complete_dir : dir, sizeof(buf)); if (complete_dir) free (complete_dir); iface_entry_set_text (buf); free (dir); } else if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *entry_text = iface_entry_get_text (); if (entry_text[0]) { char *dir = make_dir (entry_text); iface_entry_history_add (); if (dir) { /* strip trailing slash */ if (dir[strlen(dir)-1] == '/' && strcmp(dir, "/")) dir[strlen(dir)-1] = 0; go_to_dir (dir, 0); free (dir); } } iface_entry_disable (); free (entry_text); } else iface_entry_handle_key (k); } /* Request playing from the specified URL. */ static void play_from_url (const char *url) { send_int_to_srv (CMD_LOCK); change_srv_plist_serial (); send_int_to_srv (CMD_LIST_CLEAR); send_int_to_srv (CMD_LIST_ADD); send_str_to_srv (url); send_int_to_srv (CMD_PLAY); send_str_to_srv (""); send_int_to_srv (CMD_UNLOCK); } /* Return malloc()ed string that is a copy of str without leading and trailing * white spaces. */ static char *strip_white_spaces (const char *str) { char *clean; int n; assert (str != NULL); n = strlen (str); /* Strip trailing. */ while (n > 0 && isblank(str[n-1])) n--; /* Strip leading whitespace. */ while (*str && isblank(*str)) { str++; n--; } if (n > 0) { clean = (char *)xmalloc ((n + 1) * sizeof(char)); strncpy (clean, str, n); clean[n] = 0; } else clean = xstrdup (""); return clean; } static void entry_key_go_url (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *entry_text = iface_entry_get_text (); if (entry_text[0]) { char *clean_url = strip_white_spaces (entry_text); iface_entry_history_add (); if (is_url(clean_url)) play_from_url (clean_url); else error ("Not a valid URL."); free (clean_url); } free (entry_text); iface_entry_disable (); } else iface_entry_handle_key (k); } static void add_url_to_plist (const char *url) { assert (url != NULL); if (plist_find_fname(playlist, url) == -1) { send_int_to_srv (CMD_LOCK); if (options_get_bool("SyncPlaylist")) { struct plist_item *item = plist_new_item (); item->file = xstrdup (url); item->title_file = xstrdup (url); send_int_to_srv (CMD_CLI_PLIST_ADD); send_item_to_srv (item); plist_free_item_fields (item); free (item); } else { int added; added = plist_add (playlist, url); make_file_title (playlist, added, false); iface_add_to_plist (playlist, added); } /* Add to the server's playlist if the server has our * playlist. */ if (get_server_plist_serial() == plist_get_serial(playlist)) { send_int_to_srv (CMD_LIST_ADD); send_str_to_srv (url); } send_int_to_srv (CMD_UNLOCK); } else error ("URL already on the playlist"); } static void entry_key_add_url (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *entry_text = iface_entry_get_text (); if (entry_text[0]) { char *clean_url = strip_white_spaces (entry_text); iface_entry_history_add (); if (is_url(clean_url)) add_url_to_plist (clean_url); else error ("Not a valid URL."); free (clean_url); } free (entry_text); iface_entry_disable (); } else iface_entry_handle_key (k); } static void entry_key_search (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *file = iface_get_curr_file (); char *text = iface_entry_get_text (); iface_entry_disable (); if (text[0]) { if (!strcmp(file, "..")) { free (file); file = dir_up (cwd); } if (is_url(file)) play_from_url (file); else if (file_type(file) == F_DIR) go_to_dir (file, 0); else if (file_type(file) == F_PLAYLIST) go_to_playlist (file, 0, false); else play_it (file); } free (text); free (file); } else iface_entry_handle_key (k); } static void save_playlist (const char *file, const int save_serial) { iface_set_status ("Saving the playlist..."); fill_tags (playlist, TAGS_COMMENTS | TAGS_TIME, 0); if (!user_wants_interrupt()) { if (plist_save (playlist, file, save_serial)) interface_message ("Playlist saved"); } else iface_set_status ("Aborted"); iface_set_status (""); } static void entry_key_plist_save (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *text = iface_entry_get_text (); iface_entry_disable (); if (text[0]) { char *ext = ext_pos (text); char *file; /* add extension if necessary */ if (!ext || strcmp(ext, "m3u")) { char *tmp = (char *)xmalloc((strlen(text) + 5) * sizeof(char)); sprintf (tmp, "%s.m3u", text); free (text); text = tmp; } file = make_dir (text); if (file_exists(file)) { iface_make_entry (ENTRY_PLIST_OVERWRITE); iface_entry_set_file (file); } else { save_playlist (file, 0); if (iface_in_dir_menu()) reread_dir (); } free (file); } free (text); } else iface_entry_handle_key (k); } static void entry_key_plist_overwrite (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && toupper(k->key.ucs) == 'Y') { char *file = iface_entry_get_file (); assert (file != NULL); iface_entry_disable (); save_playlist (file, 0); if (iface_in_dir_menu()) reread_dir (); free (file); } else if (k->type == IFACE_KEY_CHAR && toupper(k->key.ucs) == 'N') { iface_entry_disable (); iface_message ("Not overwriting."); } } static void entry_key_user_query (const struct iface_key *k) { if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\n') { char *entry_text = iface_entry_get_text (); iface_entry_disable (); iface_user_reply (entry_text); free (entry_text); } else iface_entry_handle_key (k); } /* Handle keys while in an entry. */ static void entry_key (const struct iface_key *k) { switch (iface_get_entry_type()) { case ENTRY_GO_DIR: entry_key_go_dir (k); break; case ENTRY_GO_URL: entry_key_go_url (k); break; case ENTRY_ADD_URL: entry_key_add_url (k); break; case ENTRY_SEARCH: entry_key_search (k); break; case ENTRY_PLIST_SAVE: entry_key_plist_save (k); break; case ENTRY_PLIST_OVERWRITE: entry_key_plist_overwrite (k); break; case ENTRY_USER_QUERY: entry_key_user_query (k); break; default: abort (); /* BUG */ } } /* Update items in the menu for all items on the playlist. */ static void update_iface_menu (const enum iface_menu menu, const struct plist *plist) { int i; assert (plist != NULL); for (i = 0; i < plist->num; i++) if (!plist_deleted(plist, i)) iface_update_item (menu, plist, i); } /* Switch ReadTags options and update the menu. */ static void switch_read_tags () { if (options_get_bool ("ReadTags")) { options_set_bool ("ReadTags", false); switch_titles_file (dir_plist); switch_titles_file (playlist); iface_set_status ("ReadTags: no"); } else { options_set_bool ("ReadTags", true); ask_for_tags (dir_plist, TAGS_COMMENTS); ask_for_tags (playlist, TAGS_COMMENTS); switch_titles_tags (dir_plist); switch_titles_tags (playlist); iface_set_status ("ReadTags: yes"); } update_iface_menu (IFACE_MENU_DIR, dir_plist); update_iface_menu (IFACE_MENU_PLIST, playlist); } static void seek (const int sec) { send_int_to_srv (CMD_SEEK); send_int_to_srv (sec); } static void jump_to (const int sec) { send_int_to_srv (CMD_JUMP_TO); send_int_to_srv (sec); } static void delete_item () { char *file; if (!iface_in_plist_menu()) { error ("You can only delete an item from the playlist."); return; } assert (plist_count(playlist) > 0); file = iface_get_curr_file (); send_int_to_srv (CMD_LOCK); remove_file_from_playlist (file); send_int_to_srv (CMD_UNLOCK); free (file); } /* Select the file that is currently played. */ static void go_to_playing_file () { if (curr_file.file && file_type(curr_file.file) == F_SOUND) { if (plist_find_fname(playlist, curr_file.file) != -1) iface_switch_to_plist (); else if (plist_find_fname(dir_plist, curr_file.file) != -1) iface_switch_to_dir (); else { char *slash; char *file = xstrdup (curr_file.file); slash = strrchr (file, '/'); assert (slash != NULL); *slash = 0; if (file[0]) go_to_dir (file, 0); else go_to_dir ("/", 0); iface_switch_to_dir (); free (file); } iface_select_file (curr_file.file); } } /* Return the time like the standard time() function, but rounded i.e. if we * have 11.8 seconds, return 12 seconds. */ static time_t rounded_time () { struct timespec exact_time; time_t curr_time; if (get_realtime (&exact_time) == -1) interface_fatal ("get_realtime() failed: %s", xstrerror (errno)); curr_time = exact_time.tv_sec; if (exact_time.tv_nsec > 500000000L) curr_time += 1; return curr_time; } /* Handle silent seek key. */ static void seek_silent (const int sec) { if (curr_file.state == STATE_PLAY && curr_file.file && !is_url(curr_file.file)) { if (silent_seek_pos == -1) { silent_seek_pos = curr_file.curr_time + sec; } else silent_seek_pos += sec; silent_seek_pos = CLAMP(0, silent_seek_pos, curr_file.total_time); silent_seek_key_last = rounded_time (); iface_set_curr_time (silent_seek_pos); } } /* Move the current playlist item (direction: 1 - up, -1 - down). */ static void move_item (const int direction) { char *file; int second; char *second_file; if (!iface_in_plist_menu()) { error ("You can move only playlist items."); return; } if (!(file = iface_get_curr_file())) return; second = plist_find_fname (playlist, file); assert (second != -1); if (direction == -1) second = plist_next (playlist, second); else if (direction == 1) second = plist_prev (playlist, second); else abort (); /* BUG */ if (second == -1) { free (file); return; } second_file = plist_get_file (playlist, second); send_int_to_srv (CMD_LOCK); if (options_get_bool("SyncPlaylist")) { send_int_to_srv (CMD_CLI_PLIST_MOVE); send_str_to_srv (file); send_str_to_srv (second_file); } else swap_playlist_items (file, second_file); /* update the server's playlist */ if (get_server_plist_serial() == plist_get_serial(playlist)) { send_int_to_srv (CMD_LIST_MOVE); send_str_to_srv (file); send_str_to_srv (second_file); } send_int_to_srv (CMD_UNLOCK); free (second_file); free (file); } /* Handle releasing silent seek key. */ static void do_silent_seek () { time_t curr_time = time(NULL); if (silent_seek_pos != -1 && silent_seek_key_last < curr_time) { seek (silent_seek_pos - curr_file.curr_time - 1); silent_seek_pos = -1; iface_set_curr_time (curr_file.curr_time); } } /* Handle the 'next' command. */ static void cmd_next () { if (curr_file.state != STATE_STOP) send_int_to_srv (CMD_NEXT); else if (plist_count(playlist)) { if (plist_get_serial(playlist) != -1 || get_server_plist_serial() != plist_get_serial(playlist)) { int serial; send_int_to_srv (CMD_LOCK); send_playlist (playlist, 1); serial = get_safe_serial(); plist_set_serial (playlist, serial); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (plist_get_serial(playlist)); send_int_to_srv (CMD_UNLOCK); } send_int_to_srv (CMD_PLAY); send_str_to_srv (""); } } /* Add themes found in the directory to the list of theme files. */ static void add_themes_to_list (lists_t_strs *themes, const char *themes_dir) { DIR *dir; struct dirent *entry; assert (themes); assert (themes_dir); if (!(dir = opendir (themes_dir))) { char *err = xstrerror (errno); logit ("Can't open themes directory %s: %s", themes_dir, err); free (err); return; } while ((entry = readdir(dir))) { int rc; char file[PATH_MAX]; if (entry->d_name[0] == '.') continue; /* Filter out backup files (*~). */ if (entry->d_name[strlen(entry->d_name)-1] == '~') continue; rc = snprintf(file, sizeof(file), "%s/%s", themes_dir, entry->d_name); if (rc >= ssizeof(file)) continue; lists_strs_append (themes, file); } closedir (dir); } /* Compare two pathnames based on filename. */ static int themes_cmp (const void *a, const void *b) { int result; char *sa = *(char **)a; char *sb = *(char **)b; result = strcoll (strrchr (sa, '/') + 1, strrchr (sb, '/') + 1); if (result == 0) result = strcoll (sa, sb); return result; } /* Add themes found in the directories to the theme selection menu. * Return the number of items added. */ static int add_themes_to_menu (const char *user_themes, const char *system_themes) { int ix; lists_t_strs *themes; assert (user_themes); assert (system_themes); themes = lists_strs_new (16); add_themes_to_list (themes, user_themes); add_themes_to_list (themes, system_themes); lists_strs_sort (themes, themes_cmp); for (ix = 0; ix < lists_strs_size (themes); ix += 1) { char *file; file = lists_strs_at (themes, ix); iface_add_file (file, strrchr (file, '/') + 1, F_THEME); } lists_strs_free (themes); return ix; } static void make_theme_menu () { iface_switch_to_theme_menu (); if (add_themes_to_menu (create_file_name ("themes"), SYSTEM_THEMES_DIR) == 0) { if (!cwd[0]) enter_first_dir (); /* we were at the playlist from the startup */ else iface_switch_to_dir (); error ("No themes found."); } iface_update_theme_selection (get_current_theme ()); iface_refresh (); } /* Use theme from the currently selected file. */ static void use_theme () { char *file = iface_get_curr_file (); assert (iface_curritem_get_type() == F_THEME); if (!file) return; themes_switch_theme (file); iface_update_attrs (); iface_refresh (); free (file); } /* Handle keys while in the theme menu. */ static void theme_menu_key (const struct iface_key *k) { if (!iface_key_is_resize(k)) { enum key_cmd cmd = get_key_cmd (CON_MENU, k); switch (cmd) { case KEY_CMD_GO: use_theme (); break; case KEY_CMD_MENU_DOWN: case KEY_CMD_MENU_UP: case KEY_CMD_MENU_NPAGE: case KEY_CMD_MENU_PPAGE: case KEY_CMD_MENU_FIRST: case KEY_CMD_MENU_LAST: iface_menu_key (cmd); break; default: iface_switch_to_dir (); logit ("Bad key"); } } } /* Make sure that we have tags and a title for this file which is in a menu. */ static void make_sure_tags_exist (const char *file) { struct plist *plist; int item_num; if (file_type(file) != F_SOUND) return; if ((item_num = plist_find_fname(dir_plist, file)) != -1) plist = dir_plist; else if ((item_num = plist_find_fname(playlist, file)) != -1) plist = playlist; else return; if (!plist->items[item_num].tags || plist->items[item_num].tags->filled != (TAGS_COMMENTS | TAGS_TIME)) { int got_it = 0; send_tags_request (file, TAGS_COMMENTS | TAGS_TIME); while (!got_it) { int type = get_int_from_srv (); void *data = get_event_data (type); if (type == EV_FILE_TAGS) { struct tag_ev_response *ev = (struct tag_ev_response *)data; if (!strcmp(ev->file, file)) got_it = 1; } server_event (type, data); } } } /* Request tags from the server for a file in the playlist or the directory * menu, wait until they arrive and return them (malloc()ed). */ static struct file_tags *get_tags (const char *file) { struct plist *plist; int item_num; make_sure_tags_exist (file); if ((item_num = plist_find_fname(dir_plist, file)) != -1) plist = dir_plist; else if ((item_num = plist_find_fname(playlist, file)) != -1) plist = playlist; else return tags_new (); if (file_type(file) == F_SOUND) return tags_dup (plist->items[item_num].tags); return tags_new (); } /* Get the title of a file (malloc()ed) that is present in a menu. */ static char *get_title (const char *file) { struct plist *plist; int item_num; make_sure_tags_exist (file); if ((item_num = plist_find_fname(dir_plist, file)) != -1) plist = dir_plist; else if ((item_num = plist_find_fname(playlist, file)) != -1) plist = playlist; else return NULL; return xstrdup (plist->items[item_num].title_tags ? plist->items[item_num].title_tags : plist->items[item_num].title_file); } /* Substitute arguments for custom command that begin with '%'. * The new value is returned. */ static char *custom_cmd_substitute (const char *arg) { char *result = NULL; char *file = NULL; struct file_tags *tags = NULL; if (strlen (arg) == 2 && arg[0] == '%') { switch (arg[1]) { case 'i': file = iface_get_curr_file (); result = get_title (file); break; case 't': file = iface_get_curr_file (); tags = get_tags (file); result = xstrdup (tags->title); break; case 'a': file = iface_get_curr_file (); tags = get_tags (file); result = xstrdup (tags->album); break; case 'r': file = iface_get_curr_file (); tags = get_tags (file); result = xstrdup (tags->artist); break; case 'n': file = iface_get_curr_file (); tags = get_tags (file); result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", tags->track); break; case 'm': file = iface_get_curr_file (); tags = get_tags (file); result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", tags->time); break; case 'f': result = iface_get_curr_file (); break; case 'I': result = xstrdup (curr_file.title); break; case 'T': if (curr_file.tags && curr_file.tags->title) result = xstrdup (curr_file.tags->title); break; case 'A': if (curr_file.tags && curr_file.tags->album) result = xstrdup (curr_file.tags->album); break; case 'R': if (curr_file.tags && curr_file.tags->artist) result = xstrdup (curr_file.tags->artist); break; case 'N': if (curr_file.tags && curr_file.tags->track != -1) { result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", curr_file.tags->track); } break; case 'M': if (curr_file.tags && curr_file.tags->time != -1) { result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", curr_file.tags->time); } break; case 'F': if (curr_file.file) result = xstrdup (curr_file.file); break; case 'S': if (curr_file.file && curr_file.block_file) { result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", curr_file.block_start); } break; case 'E': if (curr_file.file && curr_file.block_file) { result = (char *) xmalloc (sizeof (char) * 16); snprintf (result, 16, "%d", curr_file.block_end); } break; default: result = xstrdup (arg); } } else result = xstrdup (arg); /* Replace nonexisting data with an empty string. */ if (!result) result = xstrdup (""); free (file); if (tags) tags_free (tags); return result; } static void run_external_cmd (char **args, const int arg_num ASSERT_ONLY) { pid_t child; assert (args != NULL); assert (arg_num >= 1); assert (args[0] != NULL); assert (args[arg_num] == NULL); iface_temporary_exit (); child = fork(); if (child == -1) error_errno ("fork() failed", errno); else { int status; if (child == 0) { /* I'm a child. */ char *err; putchar ('\n'); execvp (args[0], args); /* We have an error. */ err = xstrerror (errno); fprintf (stderr, "\nError executing %s: %s\n", args[0], err); free (err); xsleep (2, 1); exit (EXIT_FAILURE); } /* parent */ waitpid (child, &status, 0); if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { fprintf (stderr, "\nCommand exited with error (status %d).\n", WEXITSTATUS(status)); xsleep (2, 1); } iface_restore (); } } /* Exec (execvp()) a custom command (ExecCommand[1-10] options). */ static void exec_custom_command (const char *option) { char *cmd, *arg; char **args; int ix, arg_num; lists_t_strs *cmd_list, *arg_list; assert (option != NULL); cmd = options_get_str (option); if (!cmd || !cmd[0]) { error ("%s is not set", option); return; } /* Split into arguments. */ cmd_list = lists_strs_new (4); arg_num = lists_strs_tokenise (cmd_list, cmd); if (arg_num == 0) { error ("Malformed %s option", option); lists_strs_free (cmd_list); return; } arg_list = lists_strs_new (lists_strs_size (cmd_list)); for (ix = 0; ix < arg_num; ix += 1) { arg = custom_cmd_substitute (lists_strs_at (cmd_list, ix)); lists_strs_push (arg_list, arg); } lists_strs_free (cmd_list); cmd = lists_strs_fmt (arg_list, " %s"); logit ("Running command:%s", cmd); free (cmd); args = lists_strs_save (arg_list); lists_strs_free (arg_list); run_external_cmd (args, arg_num); free (args); if (iface_in_dir_menu()) reread_dir (); } static void go_to_fast_dir (const int num) { char option_name[20]; assert (RANGE(1, num, 10)); sprintf (option_name, "FastDir%d", num); if (options_get_str(option_name)) { char dir[PATH_MAX] = "/"; resolve_path (dir, sizeof(dir), options_get_str(option_name)); go_to_dir (dir, 0); } else error ("%s is not defined", option_name); } static void toggle_playlist_full_paths (void) { bool new_val = !options_get_bool ("PlaylistFullPaths"); options_set_bool ("PlaylistFullPaths", new_val); if (new_val) iface_set_status ("PlaylistFullPaths: on"); else iface_set_status ("PlaylistFullPaths: off"); update_iface_menu (IFACE_MENU_PLIST, playlist); } /* Handle key. */ static void menu_key (const struct iface_key *k) { if (iface_in_help ()) iface_handle_help_key (k); else if (iface_in_lyrics ()) iface_handle_lyrics_key (k); else if (iface_in_entry ()) entry_key (k); else if (iface_in_theme_menu ()) theme_menu_key (k); else if (!iface_key_is_resize (k)) { enum key_cmd cmd = get_key_cmd (CON_MENU, k); switch (cmd) { case KEY_CMD_QUIT_CLIENT: want_quit = QUIT_CLIENT; break; case KEY_CMD_GO: go_file (); break; case KEY_CMD_MENU_DOWN: case KEY_CMD_MENU_UP: case KEY_CMD_MENU_NPAGE: case KEY_CMD_MENU_PPAGE: case KEY_CMD_MENU_FIRST: case KEY_CMD_MENU_LAST: iface_menu_key (cmd); last_menu_move_time = time (NULL); break; case KEY_CMD_QUIT: want_quit = QUIT_SERVER; break; case KEY_CMD_STOP: send_int_to_srv (CMD_STOP); break; case KEY_CMD_NEXT: cmd_next (); break; case KEY_CMD_PREVIOUS: send_int_to_srv (CMD_PREV); break; case KEY_CMD_PAUSE: switch_pause (); break; case KEY_CMD_TOGGLE_READ_TAGS: switch_read_tags (); break; case KEY_CMD_TOGGLE_SHUFFLE: toggle_option ("Shuffle"); break; case KEY_CMD_TOGGLE_REPEAT: toggle_option ("Repeat"); break; case KEY_CMD_TOGGLE_AUTO_NEXT: toggle_option ("AutoNext"); break; case KEY_CMD_TOGGLE_MENU: toggle_menu (); break; case KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS: toggle_playlist_full_paths (); break; case KEY_CMD_PLIST_ADD_FILE: add_file_plist (); break; case KEY_CMD_PLIST_CLEAR: cmd_clear_playlist (); break; case KEY_CMD_PLIST_ADD_DIR: add_dir_plist (); break; case KEY_CMD_PLIST_REMOVE_DEAD_ENTRIES: remove_dead_entries_plist (); break; case KEY_CMD_MIXER_DEC_1: adjust_mixer (-1); break; case KEY_CMD_MIXER_DEC_5: adjust_mixer (-5); break; case KEY_CMD_MIXER_INC_5: adjust_mixer (+5); break; case KEY_CMD_MIXER_INC_1: adjust_mixer (+1); break; case KEY_CMD_SEEK_BACKWARD: seek (-options_get_int ("SeekTime")); break; case KEY_CMD_SEEK_FORWARD: seek (options_get_int ("SeekTime")); break; case KEY_CMD_HELP: iface_switch_to_help (); break; case KEY_CMD_LYRICS: iface_switch_to_lyrics (); break; case KEY_CMD_HIDE_MESSAGE: iface_disable_message (); break; case KEY_CMD_REFRESH: iface_refresh (); break; case KEY_CMD_RELOAD: if (iface_in_dir_menu ()) reread_dir (); break; case KEY_CMD_TOGGLE_SHOW_HIDDEN_FILES: options_set_bool ("ShowHiddenFiles", !options_get_bool ("ShowHiddenFiles")); if (iface_in_dir_menu ()) reread_dir (); break; case KEY_CMD_GO_MUSIC_DIR: go_to_music_dir (); break; case KEY_CMD_PLIST_DEL: delete_item (); break; case KEY_CMD_MENU_SEARCH: iface_make_entry (ENTRY_SEARCH); break; case KEY_CMD_PLIST_SAVE: if (plist_count (playlist)) iface_make_entry (ENTRY_PLIST_SAVE); else error ("The playlist is empty."); break; case KEY_CMD_TOGGLE_SHOW_TIME: toggle_show_time (); break; case KEY_CMD_TOGGLE_SHOW_FORMAT: toggle_show_format (); break; case KEY_CMD_GO_TO_PLAYING_FILE: go_to_playing_file (); break; case KEY_CMD_GO_DIR: iface_make_entry (ENTRY_GO_DIR); break; case KEY_CMD_GO_URL: iface_make_entry (ENTRY_GO_URL); break; case KEY_CMD_GO_DIR_UP: go_dir_up (); break; case KEY_CMD_WRONG: error ("Bad command"); break; case KEY_CMD_SEEK_FORWARD_5: seek_silent (options_get_int ("SilentSeekTime")); break; case KEY_CMD_SEEK_BACKWARD_5: seek_silent (-options_get_int ("SilentSeekTime")); break; case KEY_CMD_VOLUME_10: set_mixer (10); break; case KEY_CMD_VOLUME_20: set_mixer (20); break; case KEY_CMD_VOLUME_30: set_mixer (30); break; case KEY_CMD_VOLUME_40: set_mixer (40); break; case KEY_CMD_VOLUME_50: set_mixer (50); break; case KEY_CMD_VOLUME_60: set_mixer (60); break; case KEY_CMD_VOLUME_70: set_mixer (70); break; case KEY_CMD_VOLUME_80: set_mixer (80); break; case KEY_CMD_VOLUME_90: set_mixer (90); break; case KEY_CMD_MARK_START: file_info_block_mark (&curr_file.block_start); break; case KEY_CMD_MARK_END: file_info_block_mark (&curr_file.block_end); break; case KEY_CMD_FAST_DIR_1: go_to_fast_dir (1); break; case KEY_CMD_FAST_DIR_2: go_to_fast_dir (2); break; case KEY_CMD_FAST_DIR_3: go_to_fast_dir (3); break; case KEY_CMD_FAST_DIR_4: go_to_fast_dir (4); break; case KEY_CMD_FAST_DIR_5: go_to_fast_dir (5); break; case KEY_CMD_FAST_DIR_6: go_to_fast_dir (6); break; case KEY_CMD_FAST_DIR_7: go_to_fast_dir (7); break; case KEY_CMD_FAST_DIR_8: go_to_fast_dir (8); break; case KEY_CMD_FAST_DIR_9: go_to_fast_dir (9); break; case KEY_CMD_FAST_DIR_10: go_to_fast_dir (10); break; case KEY_CMD_TOGGLE_MIXER: debug ("Toggle mixer."); send_int_to_srv (CMD_TOGGLE_MIXER_CHANNEL); break; case KEY_CMD_TOGGLE_SOFTMIXER: debug ("Toggle softmixer."); send_int_to_srv (CMD_TOGGLE_SOFTMIXER); break; case KEY_CMD_TOGGLE_EQUALIZER: debug ("Toggle equalizer."); send_int_to_srv (CMD_TOGGLE_EQUALIZER); break; case KEY_CMD_EQUALIZER_REFRESH: debug ("Equalizer Refresh."); send_int_to_srv (CMD_EQUALIZER_REFRESH); break; case KEY_CMD_EQUALIZER_PREV: debug ("Equalizer Prev."); send_int_to_srv (CMD_EQUALIZER_PREV); break; case KEY_CMD_EQUALIZER_NEXT: debug ("Equalizer Next."); send_int_to_srv (CMD_EQUALIZER_NEXT); break; case KEY_CMD_TOGGLE_MAKE_MONO: debug ("Toggle Mono-Mixing."); send_int_to_srv (CMD_TOGGLE_MAKE_MONO); break; case KEY_CMD_TOGGLE_LAYOUT: iface_toggle_layout (); break; case KEY_CMD_TOGGLE_PERCENT: iface_toggle_percent (); break; case KEY_CMD_PLIST_MOVE_UP: move_item (1); break; case KEY_CMD_PLIST_MOVE_DOWN: move_item (-1); break; case KEY_CMD_ADD_STREAM: iface_make_entry (ENTRY_ADD_URL); break; case KEY_CMD_THEME_MENU: make_theme_menu (); break; case KEY_CMD_EXEC1: exec_custom_command ("ExecCommand1"); break; case KEY_CMD_EXEC2: exec_custom_command ("ExecCommand2"); break; case KEY_CMD_EXEC3: exec_custom_command ("ExecCommand3"); break; case KEY_CMD_EXEC4: exec_custom_command ("ExecCommand4"); break; case KEY_CMD_EXEC5: exec_custom_command ("ExecCommand5"); break; case KEY_CMD_EXEC6: exec_custom_command ("ExecCommand6"); break; case KEY_CMD_EXEC7: exec_custom_command ("ExecCommand7"); break; case KEY_CMD_EXEC8: exec_custom_command ("ExecCommand8"); break; case KEY_CMD_EXEC9: exec_custom_command ("ExecCommand9"); break; case KEY_CMD_EXEC10: exec_custom_command ("ExecCommand10"); break; case KEY_CMD_QUEUE_TOGGLE_FILE: queue_toggle_file (); break; case KEY_CMD_QUEUE_CLEAR: cmd_clear_queue (); break; default: abort (); } } } /* Get event from the server and handle it. */ static void get_and_handle_event () { int type; if (!get_int_from_srv_noblock(&type)) { debug ("Getting event would block."); return; } server_event (type, get_event_data(type)); } /* Handle events from the queue. */ static void dequeue_events () { struct event *e; debug ("Dequeuing events..."); while ((e = event_get_first(&events))) { server_event (e->type, e->data); event_pop (&events); } debug ("done"); } /* Action after CTRL-C was pressed. */ static void handle_interrupt () { if (iface_in_entry()) iface_entry_disable (); } void init_interface (const int sock, const int logging, lists_t_strs *args) { FILE *logfp; logit ("Starting MOC Interface"); logfp = NULL; if (logging) { logfp = fopen (INTERFACE_LOG, "a"); if (!logfp) fatal ("Can't open client log file: %s", xstrerror (errno)); } log_init_stream (logfp, INTERFACE_LOG); /* Set locale according to the environment variables. */ if (!setlocale(LC_CTYPE, "")) logit ("Could not set locale!"); srv_sock = sock; file_info_reset (&curr_file); file_info_block_init (&curr_file); init_playlists (); event_queue_init (&events); keys_init (); windows_init (); get_server_options (); update_mixer_name (); xsignal (SIGQUIT, sig_quit); xsignal (SIGTERM, sig_quit); xsignal (SIGHUP, sig_quit); xsignal (SIGINT, sig_interrupt); #ifdef SIGWINCH xsignal (SIGWINCH, sig_winch); #endif if (!lists_strs_empty (args)) { process_args (args); if (plist_count(playlist) == 0) { if (!options_get_bool("SyncPlaylist") || !use_server_playlist()) load_playlist (); send_int_to_srv (CMD_SEND_PLIST_EVENTS); } else if (options_get_bool("SyncPlaylist")) { struct plist tmp_plist; /* We have made the playlist from command line. */ /* The playlist should be now clear, but this will give * us the serial number of the playlist used by other * clients. */ plist_init (&tmp_plist); get_server_playlist (&tmp_plist); send_int_to_srv (CMD_SEND_PLIST_EVENTS); send_int_to_srv (CMD_LOCK); send_int_to_srv (CMD_CLI_PLIST_CLEAR); plist_set_serial (playlist, plist_get_serial(&tmp_plist)); plist_free (&tmp_plist); change_srv_plist_serial (); iface_set_status ("Notifying clients..."); send_items_to_clients (playlist); iface_set_status (""); plist_clear (playlist); waiting_for_plist_load = 1; send_int_to_srv (CMD_UNLOCK); /* Now enter_first_dir() should not go to the music * directory. */ options_set_bool ("StartInMusicDir", false); } } else { send_int_to_srv (CMD_SEND_PLIST_EVENTS); if (!options_get_bool("SyncPlaylist") || !use_server_playlist()) load_playlist (); enter_first_dir (); } /* Ask the server for queue. */ use_server_queue (); if (options_get_bool("SyncPlaylist")) send_int_to_srv (CMD_CAN_SEND_PLIST); update_state (); if (options_get_bool("CanStartInPlaylist") && curr_file.file && plist_find_fname(playlist, curr_file.file) != -1) iface_switch_to_plist (); } void interface_loop () { log_circular_start (); while (want_quit == NO_QUIT) { fd_set fds; int ret; struct timespec timeout = { 1, 0 }; FD_ZERO (&fds); FD_SET (srv_sock, &fds); FD_SET (STDIN_FILENO, &fds); dequeue_events (); ret = pselect (srv_sock + 1, &fds, NULL, NULL, &timeout, NULL); if (ret == -1 && !want_quit && errno != EINTR) interface_fatal ("pselect() failed: %s", xstrerror (errno)); iface_tick (); if (ret == 0) do_silent_seek (); #ifdef SIGWINCH if (want_resize) do_resize (); #endif if (ret > 0) { if (FD_ISSET(STDIN_FILENO, &fds)) { struct iface_key k; iface_get_key (&k); clear_interrupt (); menu_key (&k); } if (!want_quit) { if (FD_ISSET(srv_sock, &fds)) get_and_handle_event (); do_silent_seek (); } } else if (user_wants_interrupt()) handle_interrupt (); if (!want_quit) update_mixer_value (); } log_circular_log (); log_circular_stop (); } /* Save the current directory path to a file. */ static void save_curr_dir () { FILE *dir_file; if (!(dir_file = fopen(create_file_name("last_directory"), "w"))) { error_errno ("Can't save current directory", errno); return; } fprintf (dir_file, "%s", cwd); fclose (dir_file); } /* Save the playlist in .moc directory or remove the old playist if the * playlist is empty. */ static void save_playlist_in_moc () { char *plist_file = create_file_name (PLAYLIST_FILE); if (plist_count(playlist) && options_get_bool("SavePlaylist")) save_playlist (plist_file, 1); else unlink (plist_file); } void interface_end () { save_curr_dir (); save_playlist_in_moc (); if (want_quit == QUIT_SERVER) send_int_to_srv (CMD_QUIT); else send_int_to_srv (CMD_DISCONNECT); srv_sock = -1; windows_end (); keys_cleanup (); plist_free (dir_plist); plist_free (playlist); plist_free (queue); free (dir_plist); free (playlist); free (queue); event_queue_free (&events); logit ("Interface exited"); log_close (); } void interface_fatal (const char *format, ...) { char *msg; va_list va; va_start (va, format); msg = format_msg_va (format, va); va_end (va); windows_end (); fatal ("%s", msg); } void interface_error (const char *msg) { iface_error (msg); } void interface_cmdline_clear_plist (int server_sock) { struct plist plist; int serial; srv_sock = server_sock; /* the interface is not initialized, so set it here */ plist_init (&plist); if (options_get_bool("SyncPlaylist")) send_int_to_srv (CMD_CLI_PLIST_CLEAR); if (recv_server_plist(&plist) && plist_get_serial(&plist) == get_server_plist_serial()) { send_int_to_srv (CMD_LOCK); send_int_to_srv (CMD_GET_SERIAL); serial = get_data_int (); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (serial); send_int_to_srv (CMD_LIST_CLEAR); send_int_to_srv (CMD_UNLOCK); } unlink (create_file_name (PLAYLIST_FILE)); plist_free (&plist); } static void add_recursively (struct plist *plist, lists_t_strs *args) { int ix; for (ix = 0; ix < lists_strs_size (args); ix += 1) { int dir; char path[PATH_MAX + 1]; const char *arg; arg = lists_strs_at (args, ix); if (arg[0] != '/' && !is_url (arg)) { strncpy (path, cwd, sizeof (path)); path[sizeof (path) - 1] = 0; resolve_path (path, sizeof (path), arg); } else { strncpy (path, arg, sizeof (path)); path[sizeof (path) - 1] = 0; if (!is_url (arg)) resolve_path (path, sizeof (path), ""); } dir = is_dir (path); if (dir == 1) read_directory_recurr (path, plist); else if (is_plist_file (arg)) plist_load (plist, arg, cwd, 0); else if ((is_url (path) || is_sound_file (path)) && plist_find_fname (plist, path) == -1) { int added = plist_add (plist, path); if (is_url (path)) make_file_title (plist, added, false); } } } void interface_cmdline_append (int server_sock, lists_t_strs *args) { srv_sock = server_sock; /* the interface is not initialized, so set it here */ if (options_get_bool("SyncPlaylist")) { struct plist clients_plist; struct plist new; plist_init (&clients_plist); plist_init (&new); if (!getcwd(cwd, sizeof(cwd))) fatal ("Can't get CWD: %s", xstrerror (errno)); if (recv_server_plist(&clients_plist)) { add_recursively (&new, args); plist_sort_fname (&new); send_int_to_srv (CMD_LOCK); plist_remove_common_items (&new, &clients_plist); send_items_to_clients (&new); if (get_server_plist_serial() == plist_get_serial(&clients_plist)) send_playlist (&new, 0); send_int_to_srv (CMD_UNLOCK); } else { struct plist saved_plist; plist_init (&saved_plist); /* this checks if the file exists */ if (file_type (create_file_name (PLAYLIST_FILE)) == F_PLAYLIST) plist_load (&saved_plist, create_file_name (PLAYLIST_FILE), cwd, 1); add_recursively (&new, args); plist_sort_fname (&new); send_int_to_srv (CMD_LOCK); plist_remove_common_items (&new, &saved_plist); if (plist_get_serial(&saved_plist)) plist_set_serial(&saved_plist, get_safe_serial()); plist_set_serial(&new, plist_get_serial(&saved_plist)); send_playlist (&new, 0); send_int_to_srv (CMD_UNLOCK); plist_cat (&saved_plist, &new); if (options_get_bool("SavePlaylist")) { fill_tags (&saved_plist, TAGS_COMMENTS | TAGS_TIME, 1); plist_save (&saved_plist, create_file_name (PLAYLIST_FILE), 1); } plist_free (&saved_plist); } plist_free (&clients_plist); plist_free (&new); } } void interface_cmdline_play_first (int server_sock) { struct plist plist; srv_sock = server_sock; /* the interface is not initialized, so set it here */ if (!getcwd(cwd, sizeof(cwd))) fatal ("Can't get CWD: %s", xstrerror (errno)); plist_init (&plist); send_int_to_srv (CMD_GET_SERIAL); plist_set_serial (&plist, get_data_int()); /* the second condition will checks if the file exists */ if (!recv_server_plist(&plist) && file_type (create_file_name (PLAYLIST_FILE)) == F_PLAYLIST) plist_load (&plist, create_file_name (PLAYLIST_FILE), cwd, 1); send_int_to_srv (CMD_LOCK); if (get_server_plist_serial() != plist_get_serial(&plist)) { send_playlist (&plist, 1); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (plist_get_serial(&plist)); } send_int_to_srv (CMD_PLAY); send_str_to_srv (""); plist_free (&plist); } /* Request tags from the server, wait until they arrive and return them * (malloc()ed). This function assumes that the interface is not initialized. */ static struct file_tags *get_tags_no_iface (const char *file, const int tags_sel) { struct file_tags *tags = NULL; assert (file_type(file) == F_SOUND); send_tags_request (file, tags_sel); while (!tags) { int type = get_int_from_srv (); void *data = get_event_data (type); if (type == EV_FILE_TAGS) { struct tag_ev_response *ev = (struct tag_ev_response *)data; if (!strcmp(ev->file, file)) tags = tags_dup (ev->tags); free_tag_ev_data (ev); } else { /* We can't handle other events, since this function * is to be invoked without the interface. */ logit ("Server sent an event which I didn't expect!"); abort (); } } return tags; } void interface_cmdline_file_info (const int server_sock) { srv_sock = server_sock; /* the interface is not initialized, so set it here */ init_playlists (); file_info_reset (&curr_file); file_info_block_init (&curr_file); curr_file.state = get_state (); if (curr_file.state == STATE_STOP) puts ("State: STOP"); else { int left; char curr_time_str[32]; char time_left_str[32]; char time_str[32]; char *title; if (curr_file.state == STATE_PLAY) puts ("State: PLAY"); else if (curr_file.state == STATE_PAUSE) puts ("State: PAUSE"); curr_file.file = get_curr_file (); if (curr_file.file[0]) { /* get tags */ if (file_type(curr_file.file) == F_URL) { send_int_to_srv (CMD_GET_TAGS); curr_file.tags = get_data_tags (); } else curr_file.tags = get_tags_no_iface ( curr_file.file, TAGS_COMMENTS | TAGS_TIME); /* get the title */ if (curr_file.tags->title) title = build_title (curr_file.tags); else title = xstrdup (""); } else title = xstrdup (""); curr_file.channels = get_channels (); curr_file.rate = get_rate (); curr_file.bitrate = get_bitrate (); curr_file.curr_time = get_curr_time (); curr_file.avg_bitrate = get_avg_bitrate (); if (curr_file.tags->time != -1) sec_to_min (time_str, curr_file.tags->time); else time_str[0] = 0; if (curr_file.curr_time != -1) { sec_to_min (curr_time_str, curr_file.curr_time); if (curr_file.tags->time != -1) { sec_to_min (curr_time_str, curr_file.curr_time); left = curr_file.tags->time - curr_file.curr_time; sec_to_min (time_left_str, MAX(left, 0)); } } else { strcpy (curr_time_str, "00:00"); time_left_str[0] = 0; } printf ("File: %s\n", curr_file.file); printf ("Title: %s\n", title); if (curr_file.tags) { printf ("Artist: %s\n", curr_file.tags->artist ? curr_file.tags->artist : ""); printf ("SongTitle: %s\n", curr_file.tags->title ? curr_file.tags->title : ""); printf ("Album: %s\n", curr_file.tags->album ? curr_file.tags->album : ""); } if (curr_file.tags->time != -1) { printf ("TotalTime: %s\n", time_str); printf ("TimeLeft: %s\n", time_left_str); printf ("TotalSec: %d\n", curr_file.tags->time); } printf ("CurrentTime: %s\n", curr_time_str); printf ("CurrentSec: %d\n", curr_file.curr_time); printf ("Bitrate: %dkbps\n", MAX(curr_file.bitrate, 0)); printf ("AvgBitrate: %dkbps\n", MAX(curr_file.avg_bitrate, 0)); printf ("Rate: %dkHz\n", curr_file.rate); file_info_cleanup (&curr_file); free (title); } plist_free (dir_plist); plist_free (playlist); plist_free (queue); } void interface_cmdline_enqueue (int server_sock, lists_t_strs *args) { int ix; /* the interface is not initialized, so set it here */ srv_sock = server_sock; if (!getcwd (cwd, sizeof (cwd))) fatal ("Can't get CWD: %s", xstrerror (errno)); for (ix = 0; ix < lists_strs_size (args); ix += 1) { const char *arg; arg = lists_strs_at (args, ix); if (is_sound_file (arg) || is_url (arg)) { char *path = absolute_path (arg, cwd); send_int_to_srv (CMD_QUEUE_ADD); send_str_to_srv (path); free (path); } } } void interface_cmdline_playit (int server_sock, lists_t_strs *args) { int ix, serial; struct plist plist; srv_sock = server_sock; /* the interface is not initialized, so set it here */ if (!getcwd(cwd, sizeof(cwd))) fatal ("Can't get CWD: %s", xstrerror (errno)); plist_init (&plist); for (ix = 0; ix < lists_strs_size (args); ix += 1) { const char *arg; arg = lists_strs_at (args, ix); if (is_url(arg) || is_sound_file(arg)) { char *path = absolute_path (arg, cwd); plist_add (&plist, path); free (path); } } if (plist_count (&plist) == 0) fatal ("No files added - no sound files on command line!"); send_int_to_srv (CMD_LOCK); send_playlist (&plist, 1); send_int_to_srv (CMD_GET_SERIAL); serial = get_data_int (); send_int_to_srv (CMD_PLIST_SET_SERIAL); send_int_to_srv (serial); send_int_to_srv (CMD_UNLOCK); send_int_to_srv (CMD_PLAY); send_str_to_srv (""); plist_free (&plist); } void interface_cmdline_seek_by (int server_sock, const int seek_by) { srv_sock = server_sock; /* the interface is not initialized, so set it here */ seek (seek_by); } void interface_cmdline_jump_to (int server_sock, const int pos) { srv_sock = server_sock; /* the interface is not initialized, so set it here */ jump_to (pos); } void interface_cmdline_jump_to_percent (int server_sock, const int percent) { srv_sock = server_sock; /* the interface is not initialized, so set it here */ curr_file.file = get_curr_file (); int new_pos; if (percent >= 100) { fprintf (stderr, "Can't jump beyond the end of file.\n"); return; } if (!curr_file.file[0]) { fprintf (stderr, "Nothing is played.\n"); return; } if (file_type(curr_file.file) == F_URL) { fprintf (stderr, "Can't seek in network stream.\n"); return; } curr_file.tags = get_tags_no_iface (curr_file.file,TAGS_TIME); new_pos = (percent*curr_file.tags->time)/100; printf("Jumping to: %ds. Total time is: %ds\n", new_pos, curr_file.tags->time); jump_to (new_pos); } void interface_cmdline_adj_volume (int server_sock, const char *arg) { srv_sock = server_sock; if (arg[0] == '+') adjust_mixer (atoi (arg + 1)); else if (arg[0] == '-') adjust_mixer (atoi (arg)); /* atoi can handle '-' */ else if (arg[0] != 0) set_mixer (atoi (arg)); } void interface_cmdline_set (int server_sock, char *arg, const int val) { srv_sock = server_sock; char *last = NULL; char *tok; tok = strtok_r (arg, ",", &last); while (tok) { if (!strcasecmp (tok, "Shuffle") || !strcasecmp (tok, "s")) tok = "Shuffle"; else if (!strcasecmp (tok, "AutoNext") || !strcasecmp (tok, "n")) tok = "AutoNext"; else if (!strcasecmp (tok, "Repeat") || !strcasecmp (tok, "r")) tok = "Repeat"; else { fprintf (stderr, "Unknown option '%s'\n", tok); break; } if (val == 2) { send_int_to_srv (CMD_GET_OPTION); send_str_to_srv (tok); options_set_bool (tok, get_data_bool()); } send_int_to_srv (CMD_SET_OPTION); send_str_to_srv (tok); if (val == 2) send_bool_to_srv (!options_get_bool(tok)); else send_bool_to_srv (val); tok = strtok_r (NULL, ",", &last); } } /* Print formatted info State %state File %file Title %title Artist %artist SongTitle %song Album %album TotalTime %tt TimeLeft %tl TotalSec %ts CurrentTime %ct CurrentSec %cs Bitrate %b Rate %r */ void interface_cmdline_formatted_info (const int server_sock, const char *format_str) { typedef struct { char *state; char *file; char *title; char *artist; char *song; char *album; char *totaltime; char *timeleft; char *totalsec; char *currenttime; char *currentsec; char *bitrate; char *rate; } info_t; char curr_time_str[32]; char time_left_str[32]; char time_str[32]; char time_sec_str[16]; char curr_time_sec_str[16]; char file_bitrate_str[16]; char file_rate_str[16]; char *fmt, *str; info_t str_info; srv_sock = server_sock; /* the interface is not initialized, so set it here */ init_playlists (); file_info_reset (&curr_file); file_info_block_init (&curr_file); curr_file.state = get_state (); /* extra paranoid about struct data */ memset(&str_info, 0, sizeof(str_info)); curr_time_str[0] = time_left_str[0] = time_str[0] = time_sec_str[0] = curr_time_sec_str[0] = file_bitrate_str[0] = file_rate_str[0] = '\0'; str_info.currenttime = curr_time_str; str_info.timeleft = time_left_str; str_info.totaltime = time_str; str_info.totalsec = time_sec_str; str_info.currentsec = curr_time_sec_str; str_info.bitrate = file_bitrate_str; str_info.rate = file_rate_str; if (curr_file.state == STATE_STOP) str_info.state = "STOP"; else { int left; if (curr_file.state == STATE_PLAY) str_info.state = "PLAY"; else if (curr_file.state == STATE_PAUSE) str_info.state = "PAUSE"; curr_file.file = get_curr_file (); if (curr_file.file[0]) { /* get tags */ if (file_type(curr_file.file) == F_URL) { send_int_to_srv (CMD_GET_TAGS); curr_file.tags = get_data_tags (); } else curr_file.tags = get_tags_no_iface ( curr_file.file, TAGS_COMMENTS | TAGS_TIME); /* get the title */ if (curr_file.tags->title) str_info.title = build_title (curr_file.tags); else str_info.title = xstrdup (""); } else str_info.title = xstrdup (""); curr_file.channels = get_channels (); curr_file.rate = get_rate (); curr_file.bitrate = get_bitrate (); curr_file.curr_time = get_curr_time (); if (curr_file.tags->time != -1) sec_to_min (time_str, curr_file.tags->time); else time_str[0] = 0; if (curr_file.curr_time != -1) { sec_to_min (curr_time_str, curr_file.curr_time); if (curr_file.tags->time != -1) { sec_to_min (curr_time_str, curr_file.curr_time); left = curr_file.tags->time - curr_file.curr_time; sec_to_min (time_left_str, MAX(left, 0)); } } else { strcpy (curr_time_str, "00:00"); time_left_str[0] = 0; } str_info.file = curr_file.file; str_info.artist = curr_file.tags->artist ? curr_file.tags->artist : NULL; str_info.song = curr_file.tags->title ? curr_file.tags->title : NULL; str_info.album = curr_file.tags->album ? curr_file.tags->album : NULL; if (curr_file.tags->time != -1) snprintf(time_sec_str, sizeof(file_rate_str), "%d", curr_file.tags->time); snprintf(curr_time_sec_str, sizeof(file_rate_str), "%d", curr_file.curr_time); snprintf(file_bitrate_str, sizeof(file_rate_str), "%d", MAX(curr_file.bitrate, 0)); snprintf(file_rate_str, sizeof(file_rate_str), "%d", curr_file.rate); } /* string with formatting tags */ fmt = xstrdup(format_str); fmt = str_repl(fmt, "%state", str_info.state); fmt = str_repl(fmt, "%file", str_info.file ? str_info.file : ""); fmt = str_repl(fmt, "%title", str_info.title ? str_info.title : ""); fmt = str_repl(fmt, "%artist", str_info.artist ? str_info.artist : ""); fmt = str_repl(fmt, "%song", str_info.song ? str_info.song : ""); fmt = str_repl(fmt, "%album", str_info.album ? str_info.album : ""); fmt = str_repl(fmt, "%tt", str_info.totaltime ? str_info.totaltime : ""); fmt = str_repl(fmt, "%tl", str_info.timeleft ? str_info.timeleft : ""); fmt = str_repl(fmt, "%ts", str_info.totalsec ? str_info.totalsec : ""); fmt = str_repl(fmt, "%ct", str_info.currenttime ? str_info.currenttime : ""); fmt = str_repl(fmt, "%cs", str_info.currentsec ? str_info.currentsec : ""); fmt = str_repl(fmt, "%b", str_info.bitrate ? str_info.bitrate : ""); fmt = str_repl(fmt, "%r", str_info.rate ? str_info.rate : ""); fmt = str_repl(fmt, "\\n", "\n"); str = build_title_with_format (curr_file.tags, fmt); printf("%s\n", str); free(str); free(fmt); if (str_info.title) free(str_info.title); if (curr_file.state != STATE_STOP) file_info_cleanup (&curr_file); plist_free (dir_plist); plist_free (playlist); plist_free (queue); } moc-2.6.0~svn-r3005/interface.h0000664000076400000500000000327412424322452015246 0ustar riesebiesrc #ifndef INTERFACE_H #define INTERFACE_H #include "lists.h" #ifdef __cplusplus extern "C" { #endif /* The desired state of the client (and server). */ enum want_quit { NO_QUIT, /* don't want to quit */ QUIT_CLIENT, /* only quit the client */ QUIT_SERVER /* quit the client and the server */ }; /* Information about the currently played file. */ struct file_tags; struct file_info { char *file; struct file_tags *tags; char *title; int avg_bitrate; int bitrate; int rate; int curr_time; int total_time; int channels; int state; /* STATE_* */ char *block_file; int block_start; int block_end; }; void init_interface (const int sock, const int logging, lists_t_strs *args); void interface_loop (); void interface_end (); int user_wants_interrupt (); void interface_error (const char *msg); void interface_fatal (const char *format, ...) ATTR_PRINTF(1, 2); void interface_cmdline_clear_plist (int server_sock); void interface_cmdline_append (int server_sock, lists_t_strs *args); void interface_cmdline_play_first (int server_sock); void interface_cmdline_file_info (const int server_sock); void interface_cmdline_playit (int server_sock, lists_t_strs *args); void interface_cmdline_seek_by (int server_sock, const int seek_by); void interface_cmdline_jump_to_percent (int server_sock, const int percent); void interface_cmdline_jump_to (int server_sock, const int pos); void interface_cmdline_adj_volume (int server_sock, const char *arg); void interface_cmdline_set (int server_sock, char *arg, const int val); void interface_cmdline_formatted_info (const int server_sock, const char *format_str); void interface_cmdline_enqueue (int server_sock, lists_t_strs *args); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/interface_elements.c0000664000076400000500000031763513473665207017163 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * * Other authors: * - Kamil Tarkowski - sec_to_min_plist() */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "menu.h" #include "themes.h" #include "lists.h" #include "options.h" #include "interface_elements.h" #include "log.h" #include "files.h" #include "decoder.h" #include "keys.h" #include "playlist.h" #include "protocol.h" #include "interface.h" #include "utf8.h" #include "rcc.h" #include "lyrics.h" #ifndef PACKAGE_REVISION #define STARTUP_MESSAGE "Welcome to " PACKAGE_NAME \ " (version " PACKAGE_VERSION ")!" #else #define STARTUP_MESSAGE "Welcome to " PACKAGE_NAME \ " (version " PACKAGE_VERSION \ ", revision " PACKAGE_REVISION ")!" #endif #define HISTORY_SIZE 50 /* TODO: removing/adding a char to the entry may increase width of the text * by more than one column. */ /* Type of the side menu. */ enum side_menu_type { MENU_DIR, /* list of files in a directory */ MENU_PLAYLIST, /* a playlist of files */ MENU_THEMES, /* list of available themes */ MENU_TREE /* tree of directories */ }; struct side_menu { enum side_menu_type type; int visible; /* is it visible (are the other fields initialized) ? */ WINDOW *win; /* window for the menu */ char *title; /* title of the window */ /* Position and size of the menu in the window. */ int posx; int posy; int width; int height; int total_time; /* total time of the files on the playlist */ int total_time_for_all; /* is the total file counted for all files? */ union { struct { struct menu *main; /* visible menu */ struct menu *copy; /* copy of the menu when we display matching items while searching */ } list; /* struct menu_tree *tree;*/ } menu; }; /* State of the side menu that can be read/restored. It remembers the state * (position of the view, which file is selected, etc) of the menu. */ struct side_menu_state { struct menu_state menu_state; }; /* When used instead of the size parameter it means: fill to the end of the * window. */ #define LAYOUT_SIZE_FILL (-1) struct window_params { int x, y; int width, height; }; struct main_win_layout { struct window_params menus[3]; }; static struct main_win { WINDOW *win; char *curr_file; /* currently played file. */ int in_help; /* are we displaying help screen? */ int too_small; /* is the terminal window too small to display mocp? */ int help_screen_top; /* first visible line of the help screen. */ int in_lyrics; /* are we displaying lyrics screen? */ int lyrics_screen_top; /* first visible line of the lyrics screen. */ struct side_menu menus[3]; lists_t_strs *layout_fmt; int selected_menu; /* which menu is currently selected by the user */ } main_win; /* Bar for displaying mixer state or progress. */ struct bar { int width; /* width in chars */ float filled; /* how much is it filled in percent */ char *orig_title; /* optional title */ char title[512]; /* title with the percent value */ int show_val; /* show the title and the value? */ int show_pct; /* show percentage in the title value? */ int fill_color; /* color (ncurses attributes) of the filled part */ int empty_color; /* color of the empty part */ }; /* History for entries' values. */ struct entry_history { char *items[HISTORY_SIZE]; int num; /* number of items */ }; /* An input area where a user can type text to enter a file name etc. */ struct entry { enum entry_type type; int width; /* width of the entry part for typing */ /* The text the user types: */ wchar_t text_ucs[512]; /* unicode */ wchar_t saved_ucs[512]; /* unicode saved during history scrolling */ char *title; /* displayed title */ char *file; /* optional: file associated with the entry */ int cur_pos; /* cursor position */ int display_from; /* displaying from this char */ struct entry_history *history; /* history to use with this entry or NULL is history is not used */ int history_pos; /* current position in the history */ }; /* Type of message. */ enum message_type { NORMAL_MSG, ERROR_MSG, QUERY_MSG }; /* Save a new message for display. */ struct queued_message { struct queued_message *next; /* What type is this message? */ enum message_type type; /* Message to be displayed instead of the file's title. */ char *msg; /* Prompt to use for user query menu. */ char *prompt; /* How many seconds does the message linger? */ time_t timeout; /* The callback function and opaque data for user replies. */ t_user_reply_callback *callback; void *data; }; static struct info_win { WINDOW *win; struct queued_message *current_message; /* Message currently being displayed */ struct queued_message *queued_message_head; /* FIFO queue on which pending */ struct queued_message *queued_message_tail; /* messages get saved */ int queued_message_total; /* Total messages on queue */ int queued_message_errors; /* Error messages on queue */ int too_small; /* is the current window too small to display this widget? */ struct entry entry; int in_entry; /* are we using the entry (is the above structure initialized)? */ struct entry_history urls_history; struct entry_history dirs_history; struct entry_history user_history; /* true/false options values */ bool state_stereo; bool state_shuffle; bool state_repeat; bool state_next; bool state_net; int bitrate; /* in kbps */ int rate; /* in kHz */ int files_in_queue; /* time in seconds */ int curr_time; int total_time; int block_start; int block_end; int plist_time; /* total time of files displayed in the menu */ int plist_time_for_all; /* is the above time for all files? */ char *title; /* title of the played song. */ char status_msg[26]; /* status message */ int state_play; /* STATE_(PLAY | STOP | PAUSE) */ /* Saved user reply callback data. */ t_user_reply_callback *callback; void *data; struct bar mixer_bar; struct bar time_bar; } info_win; /* Are we running on xterm? */ static bool has_xterm = false; /* Are we running inside screen? */ static bool has_screen = false; /* Was the interface initialized? */ static int iface_initialized = 0; /* Was initscr() called? */ static int screen_initialized = 0; /* Chars used to make lines (for borders etc.). */ static struct { chtype vert; /* vertical */ chtype horiz; /* horizontal */ chtype ulcorn; /* upper left corner */ chtype urcorn; /* upper right corner */ chtype llcorn; /* lower left corner */ chtype lrcorn; /* lower right corner */ chtype rtee; /* right tee */ chtype ltee; /* left tee */ } lines; static void entry_history_init (struct entry_history *h) { assert (h != NULL); h->num = 0; } static void entry_history_add (struct entry_history *h, const char *text) { assert (h != NULL); assert (text != NULL); if (strlen (text) != strspn (text, " ")) { if (h->num == 0 || strcmp (text, h->items[h->num - 1])) { if (h->num < HISTORY_SIZE) h->items[h->num++] = xstrdup (text); else { free (h->items[0]); memmove (h->items, h->items + 1, (HISTORY_SIZE - 1) * sizeof (char *)); h->items[h->num - 1] = xstrdup (text); } } } } static void entry_history_replace (struct entry_history *h, int num, const char *text) { assert (h != NULL); assert (LIMIT(num, h->num)); assert (text != NULL); if (strlen (text) != strspn (text, " ") && strcmp (h->items[num], text)) { free (h->items[num]); h->items[num] = xstrdup (text); } } static void entry_history_clear (struct entry_history *h) { int i; assert (h != NULL); for (i = 0; i < h->num; i++) free (h->items[i]); h->num = 0; } static int entry_history_nitems (const struct entry_history *h) { assert (h != NULL); return h->num; } static char *entry_history_get (const struct entry_history *h, const int num) { assert (h != NULL); assert (LIMIT(num, h->num)); return xstrdup (h->items[num]); } /* Draw the entry. Use this function at the end of screen drawing * because it sets the cursor position in the right place. */ static void entry_draw (const struct entry *e, WINDOW *w, const int posx, const int posy) { char *text; wchar_t *text_ucs; int len; assert (e != NULL); assert (w != NULL); assert (posx >= 0); assert (posy >= 0); wmove (w, posy, posx); wattrset (w, get_color(CLR_ENTRY_TITLE)); xwprintw (w, "%s", e->title); wattrset (w, get_color(CLR_ENTRY)); len = wcslen(e->text_ucs) - e->display_from; text_ucs = (wchar_t *)xmalloc(sizeof(wchar_t) * (len + 1)); memcpy (text_ucs, e->text_ucs + e->display_from, sizeof(wchar_t) * (len + 1)); if (len > e->width) text_ucs[e->width] = L'\0'; len = wcstombs (NULL, text_ucs, -1) + 1; assert (len >= 1); text = (char *)xmalloc (len); wcstombs (text, text_ucs, len); xwprintw (w, " %-*s", e->width, text); /* Move the cursor */ wmove (w, posy, e->cur_pos - e->display_from + strwidth(e->title) + posx + 1); free (text); free (text_ucs); } static void entry_init (struct entry *e, const enum entry_type type, const int width, struct entry_history *history, const char *prompt) { const char *title; assert (e != NULL); switch (type) { case ENTRY_SEARCH: title = "SEARCH"; break; case ENTRY_PLIST_SAVE: title = "SAVE PLAYLIST"; break; case ENTRY_GO_DIR: title = "GO"; break; case ENTRY_GO_URL: title = "URL"; break; case ENTRY_ADD_URL: title = "ADD URL"; break; case ENTRY_PLIST_OVERWRITE: title = "File exists, overwrite?"; break; case ENTRY_USER_QUERY: title = prompt; break; default: abort (); } e->type = type; e->text_ucs[0] = L'\0'; e->saved_ucs[0] = L'\0'; e->file = NULL; e->title = xmalloc (strlen (title) + 2); strcpy (e->title, title); if (e->title[strlen (e->title) - 1] != ':' && e->title[strlen (e->title) - 1] != '?') strcat (e->title, ":"); e->width = width - strwidth(title); e->cur_pos = 0; e->display_from = 0; e->history = history; if (history) e->history_pos = history->num; } static enum entry_type entry_get_type (const struct entry *e) { assert (e != NULL); return e->type; } /* Set the entry text as UCS. Move the cursor to the end. */ static void entry_set_text_ucs (struct entry *e, const wchar_t *text) { int width, len; assert (e != NULL); len = MIN (wcslen (text) + 1, ARRAY_SIZE (e->text_ucs)); wmemcpy (e->text_ucs, text, len); e->text_ucs[ARRAY_SIZE (e->text_ucs) - 1] = L'\0'; width = wcswidth (e->text_ucs, WIDTH_MAX); e->cur_pos = wcslen (e->text_ucs); e->display_from = 0; if (e->cur_pos > e->width) e->display_from = width - e->width; } /* Set the entry text. */ static void entry_set_text (struct entry *e, const char *text) { wchar_t text_ucs[ARRAY_SIZE (e->text_ucs)]; assert (e != NULL); mbstowcs (text_ucs, text, ARRAY_SIZE (e->text_ucs)); e->text_ucs[ARRAY_SIZE (e->text_ucs) - 1] = L'\0'; entry_set_text_ucs (e, text_ucs); } /* Add a char to the entry where the cursor is placed. */ static void entry_add_char (struct entry *e, const wchar_t c) { size_t len; assert (e != NULL); len = wcslen (e->text_ucs); if (len >= ARRAY_SIZE(e->text_ucs) - sizeof(wchar_t)) return; memmove (e->text_ucs + e->cur_pos + 1, e->text_ucs + e->cur_pos, (len - e->cur_pos + 1) * sizeof(e->text_ucs[0])); e->text_ucs[e->cur_pos] = c; e->cur_pos++; if (e->cur_pos - e->display_from > e->width) e->display_from++; } /* Delete 'count' chars before the cursor. */ static void entry_del_chars (struct entry *e, int count) { assert (e != NULL); assert (e->cur_pos > 0); int width = wcslen (e->text_ucs); if (e->cur_pos < count) count = e->cur_pos; memmove (e->text_ucs + e->cur_pos - count, e->text_ucs + e->cur_pos, (width - e->cur_pos) * sizeof (e->text_ucs[0])); width -= count; e->text_ucs[width] = L'\0'; e->cur_pos -= count; if (e->cur_pos < e->display_from) e->display_from = e->cur_pos; /* Can we show more after deleting the chars? */ if (e->display_from > 0 && width - e->display_from < e->width) e->display_from = width - e->width; if (e->display_from < 0) e->display_from = 0; } /* Delete the char before the cursor. */ static void entry_back_space (struct entry *e) { assert (e != NULL); if (e->cur_pos > 0) entry_del_chars (e, 1); } /* Delete the char under the cursor. */ static void entry_del_char (struct entry *e) { int len; assert (e != NULL); len = wcslen (e->text_ucs); if (e->cur_pos < len) { e->cur_pos += 1; entry_del_chars (e, 1); } } /* Delete the chars from cursor to start of line. */ static void entry_del_to_start (struct entry *e) { assert (e != NULL); if (e->cur_pos > 0) entry_del_chars (e, e->cur_pos); } /* Delete the chars from cursor to end of line. */ static void entry_del_to_end (struct entry *e) { int len; assert (e != NULL); len = wcslen (e->text_ucs); if (e->cur_pos < len) { int count; count = len - e->cur_pos; e->cur_pos = len; entry_del_chars (e, count); } } /* Move the cursor one char left. */ static void entry_curs_left (struct entry *e) { assert (e != NULL); if (e->cur_pos > 0) { e->cur_pos--; if (e->cur_pos < e->display_from) e->display_from--; } } /* Move the cursor one char right. */ static void entry_curs_right (struct entry *e) { int width; assert (e != NULL); width = wcslen (e->text_ucs); if (e->cur_pos < width) { e->cur_pos++; if (e->cur_pos > e->width + e->display_from) e->display_from++; } } /* Move the cursor to the end of the entry text. */ static void entry_end (struct entry *e) { int width; assert (e != NULL); width = wcslen (e->text_ucs); e->cur_pos = width; if (width > e->width) e->display_from = width - e->width; else e->display_from = 0; } /* Move the cursor to the beginning of the entry field. */ static void entry_home (struct entry *e) { assert (e != NULL); e->display_from = 0; e->cur_pos = 0; } static void entry_resize (struct entry *e, const int width) { assert (e != NULL); assert (width > 0); e->width = width - strlen (e->title); entry_end (e); } static char *entry_get_text (const struct entry *e) { char *text; int len; assert (e != NULL); len = wcstombs (NULL, e->text_ucs, -1) + 1; assert (len >= 1); text = (char *) xmalloc (sizeof (char) * len); wcstombs (text, e->text_ucs, len); return text; } /* Copy the previous history item to the entry if available, move the entry * history position down. */ static void entry_set_history_up (struct entry *e) { assert (e != NULL); assert (e->history != NULL); if (e->history_pos > 0) { char *t; if (e->history_pos == entry_history_nitems (e->history)) wmemcpy (e->saved_ucs, e->text_ucs, wcslen (e->text_ucs) + 1); else { t = entry_get_text (e); entry_history_replace (e->history, e->history_pos, t); free (t); } e->history_pos--; t = entry_history_get (e->history, e->history_pos); entry_set_text (e, t); free (t); } } /* Copy the next history item to the entry if available, move the entry history * position down. */ static void entry_set_history_down (struct entry *e) { assert (e != NULL); assert (e->history != NULL); if (e->history_pos < entry_history_nitems (e->history)) { char *t; t = entry_get_text (e); entry_history_replace (e->history, e->history_pos, t); free (t); e->history_pos++; if (e->history_pos == entry_history_nitems (e->history)) entry_set_text_ucs (e, e->saved_ucs); else { t = entry_history_get (e->history, e->history_pos); entry_set_text (e, t); free (t); } } } static void entry_set_file (struct entry *e, const char *file) { assert (e != NULL); assert (file != NULL); if (e->file) free (e->file); e->file = xstrdup (file); } static char *entry_get_file (const struct entry *e) { return xstrdup (e->file); } static void entry_destroy (struct entry *e) { assert (e != NULL); if (e->file) free (e->file); if (e->title) free (e->title); } static void entry_add_text_to_history (struct entry *e) { char *text; assert (e != NULL); assert (e->history); text = entry_get_text (e); entry_history_add (e->history, text); free (text); } /* Return the list menu height inside the side menu. */ static int side_menu_get_menu_height (const struct side_menu *m) { if (m->posy + m->height == LINES - 4) return m->height - 1; return m->height - 2; } static void side_menu_init_menu (struct side_menu *m) { assert (m != NULL); m->menu.list.main = menu_new (m->win, m->posx + 1, m->posy + 1, m->width - 2, side_menu_get_menu_height (m)); } static void side_menu_init (struct side_menu *m, const enum side_menu_type type, WINDOW *parent_win, const struct window_params *wp) { assert (m != NULL); assert (parent_win != NULL); assert (wp != NULL); assert (wp->width >= 8); assert (wp->height >= 3); m->type = type; m->win = parent_win; m->posx = wp->x; m->posy = wp->y; m->height = wp->height; m->width = wp->width; m->title = NULL; m->total_time = 0; m->total_time_for_all = 0; if (type == MENU_DIR || type == MENU_PLAYLIST) { side_menu_init_menu (m); m->menu.list.copy = NULL; menu_set_items_numbering (m->menu.list.main, type == MENU_PLAYLIST && options_get_bool("PlaylistNumbering")); menu_set_show_format (m->menu.list.main, options_get_bool("ShowFormat")); menu_set_show_time (m->menu.list.main, strcasecmp(options_get_symb("ShowTime"), "no")); menu_set_info_attr_normal (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO)); menu_set_info_attr_sel (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_SELECTED)); menu_set_info_attr_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED)); menu_set_info_attr_sel_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED_SELECTED)); } else if (type == MENU_THEMES) { side_menu_init_menu (m); m->menu.list.copy = NULL; } else abort (); m->visible = 1; } static void side_menu_destroy (struct side_menu *m) { assert (m != NULL); if (m->visible) { if (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES) { menu_free (m->menu.list.main); if (m->menu.list.copy) menu_free (m->menu.list.copy); } else abort (); if (m->title) free (m->title); m->visible = 0; } } static void side_menu_set_title (struct side_menu *m, const char *title) { assert (m != NULL); assert (title != NULL); if (m->title) free (m->title); m->title = xstrdup (title); } /* Parse one layout coordinate from "0,2,54%,1" and put it in val. * Max is the maximum value of the field. It's also used when processing * percent values. * Return false on error. */ static bool parse_layout_coordinate (const char *fmt, int *val, const int max) { long v; const char *e = fmt; if (!strcasecmp (fmt, "FILL")) { *val = LAYOUT_SIZE_FILL; return true; } v = strtol (fmt, (char **)&e, 10); if (e == fmt) return false; if (*e == '%') v = lroundf (max * v / 100.0 - 0.1); *val = v; if (!RANGE(0, *val, max)) { logit ("Coordinate out of range - %d is not in (0, %d)", *val, max); return false; } return true; } /* Parse the layout string. Return false on error. */ static bool parse_layout (struct main_win_layout *l, lists_t_strs *fmt) { int ix; bool result = false; lists_t_strs *format; assert (l != NULL); assert (fmt != NULL); /* default values */ l->menus[0].x = 0; l->menus[0].y = 0; l->menus[0].width = COLS; l->menus[0].height = LINES - 4; l->menus[1] = l->menus[0]; l->menus[2] = l->menus[0]; format = lists_strs_new (6); for (ix = 0; ix < lists_strs_size (fmt); ix += 1) { const char *menu, *name; struct window_params p; lists_strs_clear (format); menu = lists_strs_at (fmt, ix); if (lists_strs_split (format, menu, "(,)") != 5) goto err; name = lists_strs_at (format, 0); if (!parse_layout_coordinate (lists_strs_at (format, 1), &p.x, COLS)) { logit ("Coordinate parse error when parsing X"); goto err; } if (!parse_layout_coordinate (lists_strs_at (format, 2), &p.y, LINES - 4)) { logit ("Coordinate parse error when parsing Y"); goto err; } if (!parse_layout_coordinate (lists_strs_at (format, 3), &p.width, COLS)) { logit ("Coordinate parse error when parsing width"); goto err; } if (!parse_layout_coordinate (lists_strs_at (format, 4), &p.height, LINES - 4)) { logit ("Coordinate parse error when parsing height"); goto err; } if (p.width == LAYOUT_SIZE_FILL) p.width = COLS - p.x; if (p.height == LAYOUT_SIZE_FILL) p.height = LINES - 4 - p.y; if (p.width < 15) { logit ("Width is less than 15"); goto err; } if (p.height < 2) { logit ("Height is less than 2"); goto err; } if (p.x + p.width > COLS) { logit ("X + width is more than COLS (%d)", COLS); goto err; } if (p.y + p.height > LINES - 4) { logit ("Y + height is more than LINES - 4 (%d)", LINES - 4); goto err; } if (!strcmp(name, "directory")) l->menus[MENU_DIR] = p; else if (!strcmp(name, "playlist")) l->menus[MENU_PLAYLIST] = p; else { logit ("Bad subwindow name '%s'", name); goto err; } } result = true; err: lists_strs_free (format); return result; } static void main_win_init (struct main_win *w, lists_t_strs *layout_fmt) { struct main_win_layout l; bool rc ASSERT_ONLY; assert (w != NULL); w->win = newwin (LINES - 4, COLS, 0, 0); wbkgd (w->win, get_color(CLR_BACKGROUND)); nodelay (w->win, TRUE); keypad (w->win, TRUE); w->curr_file = NULL; w->in_help = 0; w->in_lyrics = 0; w->too_small = 0; w->help_screen_top = 0; w->lyrics_screen_top = 0; w->layout_fmt = layout_fmt; rc = parse_layout (&l, layout_fmt); assert (rc); side_menu_init (&w->menus[0], MENU_DIR, w->win, &l.menus[0]); side_menu_init (&w->menus[1], MENU_PLAYLIST, w->win, &l.menus[1]); side_menu_set_title (&w->menus[1], "Playlist"); w->menus[2].visible = 0; w->selected_menu = 0; } static void main_win_destroy (struct main_win *w) { assert (w != NULL); side_menu_destroy (&w->menus[0]); side_menu_destroy (&w->menus[1]); side_menu_destroy (&w->menus[2]); if (w->win) delwin (w->win); if (w->curr_file) free (w->curr_file); } /* Make a title suitable to display in a menu from the title of a playlist item. * Returned memory is malloc()ed. * made_from tags - was the playlist title made from tags? * full_paths - If the title is the file name, use the full path? */ static char *make_menu_title (const char *plist_title, const int made_from_tags, const int full_path) { char *title = xstrdup (plist_title); if (!made_from_tags) { if (!full_path && !is_url (title)) { /* Use only the file name instead of the full path. */ char *slash = strrchr (title, '/'); if (slash && slash != title) { char *old_title = title; title = xstrdup (slash + 1); free (old_title); } } } return title; } /* Add an item from the playlist to the menu. * If full_paths has non-zero value, full paths will be displayed instead of * just file names. * Return a non-zero value if the added item is visible on the screen. */ static int add_to_menu (struct menu *menu, const struct plist *plist, const int num, const int full_paths) { bool made_from_tags; struct menu_item *added; const struct plist_item *item = &plist->items[num]; char *title; const char *type_name; made_from_tags = (options_get_bool ("ReadTags") && item->title_tags); if (made_from_tags) title = make_menu_title (item->title_tags, 1, 0); else title = make_menu_title (item->title_file, 0, full_paths); added = menu_add (menu, title, plist_file_type (plist, num), item->file); free (title); if (item->tags && item->tags->time != -1) { char time_str[32]; sec_to_min (time_str, item->tags->time); menu_item_set_time (added, time_str); } menu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_FILE)); menu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_FILE_SELECTED)); menu_item_set_attr_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED)); menu_item_set_attr_sel_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED_SELECTED)); if (!(type_name = file_type_name(item->file))) type_name = ""; menu_item_set_format (added, type_name); menu_item_set_queue_pos (added, item->queue_pos); if (full_paths && !made_from_tags) menu_item_set_align (added, MENU_ALIGN_RIGHT); return menu_is_visible (menu, added); } static void side_menu_clear (struct side_menu *m) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); assert (m->menu.list.main != NULL); assert (m->menu.list.copy == NULL); menu_free (m->menu.list.main); side_menu_init_menu (m); menu_set_items_numbering (m->menu.list.main, m->type == MENU_PLAYLIST && options_get_bool("PlaylistNumbering")); menu_set_show_format (m->menu.list.main, options_get_bool("ShowFormat")); menu_set_show_time (m->menu.list.main, strcasecmp(options_get_symb("ShowTime"), "no")); menu_set_info_attr_normal (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO)); menu_set_info_attr_sel (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_SELECTED)); menu_set_info_attr_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED)); menu_set_info_attr_sel_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED_SELECTED)); } /* Fill the directory or playlist side menu with this content. */ static void side_menu_make_list_content (struct side_menu *m, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists, const int add_up_dir) { struct menu_item *added; int i; assert (m != NULL); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); assert (m->menu.list.main != NULL); assert (m->menu.list.copy == NULL); side_menu_clear (m); if (add_up_dir) { added = menu_add (m->menu.list.main, "../", F_DIR, ".."); menu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_DIR)); menu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_DIR_SELECTED)); } if (dirs) for (i = 0; i < lists_strs_size (dirs) ; i++) { char title[PATH_MAX]; #ifdef HAVE_RCC char *t_str = NULL; if (options_get_bool("UseRCCForFilesystem")) { strcpy (title, strrchr (lists_strs_at (dirs, i), '/') + 1); strcat (title, "/"); t_str = xstrdup (title); t_str = rcc_reencode (t_str); snprintf(title, PATH_MAX, "%s", t_str); free(t_str); } else #endif if (options_get_bool ("FileNamesIconv")) { char *conv_title = files_iconv_str ( strrchr (lists_strs_at (dirs, i), '/') + 1); strcpy (title, conv_title); strcat (title, "/"); free (conv_title); } else { strcpy (title, strrchr (lists_strs_at (dirs, i), '/') + 1); strcat (title, "/"); } added = menu_add (m->menu.list.main, title, F_DIR, lists_strs_at (dirs, i)); menu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_DIR)); menu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_DIR_SELECTED)); } if (playlists) for (i = 0; i < lists_strs_size (playlists); i++){ added = menu_add (m->menu.list.main, strrchr (lists_strs_at (playlists, i), '/') + 1, F_PLAYLIST, lists_strs_at (playlists, i)); menu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_PLAYLIST)); menu_item_set_attr_sel (added, get_color( CLR_MENU_ITEM_PLAYLIST_SELECTED)); } /* playlist items */ for (i = 0; i < files->num; i++) { if (!plist_deleted(files, i)) add_to_menu (m->menu.list.main, files, i, m->type == MENU_PLAYLIST && options_get_bool("PlaylistFullPaths")); } m->total_time = plist_total_time (files, &m->total_time_for_all); } static void clear_area (WINDOW *w, const int posx, const int posy, const int width, const int height) { int y; char line[512]; assert (width < ssizeof(line)); memset (line, ' ', width); line[width] = 0; wattrset (w, get_color(CLR_BACKGROUND)); for (y = posy; y < posy + height; y++) { wmove (w, y, posx); xwaddstr (w, line); } } static void side_menu_draw_frame (const struct side_menu *m) { char *title; assert (m != NULL); assert (m->visible); if (m->title) { if ((int)strwidth(m->title) > m->width - 4) { char *tail; tail = xstrtail (m->title, m->width - 7); title = (char *)xmalloc (strlen(tail) + 4); sprintf (title, "...%s", tail); free (tail); } else title = xstrdup (m->title); } else title = NULL; /* Border */ wattrset (m->win, get_color(CLR_FRAME)); /* upper left corner */ wmove (m->win, m->posy, m->posx); waddch (m->win, lines.ulcorn); /* upper line */ whline (m->win, lines.horiz, m->width - 2); /* upper right corner */ wmove (m->win, m->posy, m->posx + m->width - 1); waddch (m->win, lines.urcorn); /* left line */ wmove (m->win, m->posy + 1, m->posx); wvline (m->win, lines.vert, m->height - 1); /* right line */ wmove (m->win, m->posy + 1, m->posx + m->width - 1); wvline (m->win, lines.vert, m->height - 1); if (m->posy + m->height < LINES - 4) { /* bottom left corner */ wmove (m->win, m->posy + m->height - 1, m->posx); waddch (m->win, lines.llcorn); /* bottom line */ whline (m->win, lines.horiz, m->width - 2); /* bottom right corner */ wmove (m->win, m->posy + m->height - 1, m->posx + m->width - 1); waddch (m->win, lines.lrcorn); } /* The title */ if (title) { wmove (m->win, m->posy, m->posx + m->width / 2 - strwidth(title) / 2 - 1); wattrset (m->win, get_color(CLR_FRAME)); waddch (m->win, lines.rtee); wattrset (m->win, get_color(CLR_WIN_TITLE)); xwaddstr (m->win, title); wattrset (m->win, get_color(CLR_FRAME)); waddch (m->win, lines.ltee); free (title); } } static void side_menu_draw (const struct side_menu *m, const int active) { assert (m != NULL); assert (m->visible); clear_area (m->win, m->posx, m->posy, m->width, m->height); side_menu_draw_frame (m); if (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES) { menu_draw (m->menu.list.main, active); if (options_get_bool("UseCursorSelection")) menu_set_cursor (m->menu.list.main); } else abort (); } static void side_menu_cmd (struct side_menu *m, const enum key_cmd cmd) { assert (m != NULL); assert (m->visible); if (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES) { switch (cmd) { case KEY_CMD_MENU_DOWN: menu_driver (m->menu.list.main, REQ_DOWN); break; case KEY_CMD_MENU_UP: menu_driver (m->menu.list.main, REQ_UP); break; case KEY_CMD_MENU_NPAGE: menu_driver (m->menu.list.main, REQ_PGDOWN); break; case KEY_CMD_MENU_PPAGE: menu_driver (m->menu.list.main, REQ_PGUP); break; case KEY_CMD_MENU_FIRST: menu_driver (m->menu.list.main, REQ_TOP); break; case KEY_CMD_MENU_LAST: menu_driver (m->menu.list.main, REQ_BOTTOM); break; default: abort (); } } else abort (); } static enum file_type side_menu_curritem_get_type (const struct side_menu *m) { struct menu_item *mi; assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES); mi = menu_curritem (m->menu.list.main); if (mi) return menu_item_get_type (mi); return F_OTHER; } static char *side_menu_get_curr_file (const struct side_menu *m) { struct menu_item *mi; assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES); mi = menu_curritem (m->menu.list.main); if (mi) return menu_item_get_file (mi); return NULL; } static struct side_menu *find_side_menu (struct main_win *w, const enum side_menu_type type) { size_t ix; assert (w != NULL); for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) { struct side_menu *m = &w->menus[ix]; if (m->visible && m->type == type) return m; } abort (); /* menu not found - BUG */ } static void side_menu_set_curr_item_title (struct side_menu *m, const char *title) { assert (m != NULL); assert (m->visible); assert (title != NULL); menu_setcurritem_title (m->menu.list.main, title); } /* Update menu item using the playlist item. */ static void update_menu_item (struct menu_item *mi, const struct plist *plist, const int n, const int full_path) { bool made_from_tags; char *title; const struct plist_item *item; assert (mi != NULL); assert (plist != NULL); assert (n >= 0); item = &plist->items[n]; if (item->tags && item->tags->time != -1) { char time_str[32]; sec_to_min (time_str, item->tags->time); menu_item_set_time (mi, time_str); } else menu_item_set_time (mi, ""); made_from_tags = (options_get_bool ("ReadTags") && item->title_tags); if (made_from_tags) title = make_menu_title (item->title_tags, 1, 0); else title = make_menu_title (item->title_file, 0, full_path); menu_item_set_title (mi, title); if (full_path && !made_from_tags) menu_item_set_align (mi, MENU_ALIGN_RIGHT); else menu_item_set_align (mi, MENU_ALIGN_LEFT); menu_item_set_queue_pos (mi, item->queue_pos); free (title); } /* Update item title and time for this item if it's present on this menu. * Return a non-zero value if the item is visible. */ static int side_menu_update_item (struct side_menu *m, const struct plist *plist, const int n) { struct menu_item *mi; int visible = 0; char *file; assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); assert (plist != NULL); assert (LIMIT(n, plist->num)); file = plist_get_file (plist, n); assert (file != NULL); if ((mi = menu_find(m->menu.list.main, file))) { update_menu_item (mi, plist, n, m->type == MENU_PLAYLIST && options_get_bool("PlaylistFullpaths")); visible = menu_is_visible (m->menu.list.main, mi); } if (m->menu.list.copy && (mi = menu_find(m->menu.list.copy, file))) { update_menu_item (mi, plist, n, m->type == MENU_PLAYLIST && options_get_bool("PlaylistFullpaths")); visible = visible || menu_is_visible (m->menu.list.main, mi); } free (file); m->total_time = plist_total_time (plist, &m->total_time_for_all); return visible; } static void side_menu_unmark_file (struct side_menu *m) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_unmark_item (m->menu.list.main); if (m->menu.list.copy) menu_unmark_item (m->menu.list.copy); } static void side_menu_mark_file (struct side_menu *m, const char *file) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_mark_item (m->menu.list.main, file); if (m->menu.list.copy) menu_mark_item (m->menu.list.copy, file); } static void side_menu_add_file (struct side_menu *m, const char *file, const char *title, const enum file_type type) { struct menu_item *added; added = menu_add (m->menu.list.main, title, type, file); menu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_FILE)); menu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_FILE_SELECTED)); menu_item_set_attr_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED)); menu_item_set_attr_sel_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED_SELECTED)); } static int side_menu_add_plist_item (struct side_menu *m, const struct plist *plist, const int num) { int visible; assert (m != NULL); assert (plist != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); visible = add_to_menu (m->menu.list.copy ? m->menu.list.copy : m->menu.list.main, plist, num, m->type == MENU_PLAYLIST && options_get_bool("PlaylistFullPaths")); m->total_time = plist_total_time (plist, &m->total_time_for_all); return visible; } static int side_menu_is_time_for_all (const struct side_menu *m) { assert (m != NULL); assert (m->visible); return m->total_time_for_all; } static int side_menu_get_files_time (const struct side_menu *m) { assert (m != NULL); assert (m->visible); return m->total_time; } static void side_menu_update_show_time (struct side_menu *m) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_set_show_time (m->menu.list.main, strcasecmp(options_get_symb("ShowTime"), "no")); } static void side_menu_update_show_format (struct side_menu *m) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_set_show_format (m->menu.list.main, options_get_bool("ShowFormat")); } static void side_menu_get_state (const struct side_menu *m, struct side_menu_state *st) { assert (m != NULL); assert (st != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_get_state (m->menu.list.main, &st->menu_state); } static void side_menu_set_state (struct side_menu *m, const struct side_menu_state *st) { assert (m != NULL); assert (st != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_set_state (m->menu.list.main, &st->menu_state); } static void side_menu_del_item (struct side_menu *m, const char *file) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); menu_del_item (m->menu.list.copy ? m->menu.list.copy : m->menu.list.main, file); } static void side_menu_set_plist_time (struct side_menu *m, const int time, const int time_for_all) { assert (m != NULL); assert (time >= 0); assert (m->type == MENU_DIR || m->type == MENU_PLAYLIST); m->total_time = time; m->total_time_for_all = time_for_all; } /* Replace the menu with one having only those items which contain 'pattern'. * If no items match, don't do anything. * Return the number of matching items. */ static int side_menu_filter (struct side_menu *m, const char *pattern) { struct menu *filtered_menu; assert (m != NULL); assert (pattern != NULL); assert (m->menu.list.main != NULL); filtered_menu = menu_filter_pattern (m->menu.list.copy ? m->menu.list.copy : m->menu.list.main, pattern); if (menu_nitems(filtered_menu) == 0) { menu_free (filtered_menu); return 0; } if (m->menu.list.copy) menu_free (m->menu.list.main); else m->menu.list.copy = m->menu.list.main; m->menu.list.main = filtered_menu; return menu_nitems (filtered_menu); } static void side_menu_use_main (struct side_menu *m) { assert (m != NULL); assert (m->menu.list.main != NULL); if (m->menu.list.copy) { menu_free (m->menu.list.main); m->menu.list.main = m->menu.list.copy; m->menu.list.copy = NULL; } } static void side_menu_make_visible (struct side_menu *m, const char *file) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_PLAYLIST || m->type == MENU_DIR); assert (file != NULL); if (!m->menu.list.copy) menu_make_visible (m->menu.list.main, file); } static void side_menu_swap_items (struct side_menu *m, const char *file1, const char *file2) { assert (m != NULL); assert (m->visible); assert (m->type == MENU_PLAYLIST || m->type == MENU_DIR); assert (file1 != NULL); assert (file2 != NULL); assert (m->menu.list.main != NULL); assert (m->menu.list.copy == NULL); menu_swap_items (m->menu.list.main, file1, file2); } static void side_menu_select_file (struct side_menu *m, const char *file) { assert (m != NULL); assert (file != NULL); if (m->type == MENU_DIR || m->type == MENU_PLAYLIST) menu_setcurritem_file (m->menu.list.main, file); else abort (); } static void side_menu_resize (struct side_menu *m, const struct window_params *wp) { assert (m != NULL); m->posx = wp->x; m->posy = wp->y; m->height = wp->height; m->width = wp->width; if (m->type == MENU_DIR || m->type == MENU_PLAYLIST || m->type == MENU_THEMES) { menu_update_size (m->menu.list.main, m->posx + 1, m->posy + 1, m->width - 2, side_menu_get_menu_height(m)); if (m->menu.list.copy) menu_update_size (m->menu.list.copy, m->posx + 1, m->posy + 1, m->width - 2, side_menu_get_menu_height(m)); } else abort (); } static void main_win_draw_too_small_screen (const struct main_win *w) { assert (w != NULL); assert (w->too_small); werase (w->win); wbkgd (w->win, get_color(CLR_BACKGROUND)); wmove (w->win, 0, 0); wattrset (w->win, get_color(CLR_MESSAGE)); xmvwaddstr (w->win, LINES/2, COLS/2 - (sizeof("...TERMINAL IS TOO SMALL...")-1)/2, "...TERMINAL TOO SMALL..."); } static void main_win_draw_help_screen (const struct main_win *w) { int i; int max_lines; int help_lines; char **help; assert (w != NULL); assert (w->in_help); max_lines = w->help_screen_top + LINES - 6; help = get_keys_help (&help_lines); werase (w->win); wbkgd (w->win, get_color(CLR_BACKGROUND)); wmove (w->win, 0, 0); if (w->help_screen_top != 0) { wattrset (w->win, get_color(CLR_MESSAGE)); xmvwaddstr (w->win, 0, COLS/2 - (sizeof("...MORE...")-1)/2, "...MORE..."); } wmove (w->win, 1, 0); wattrset (w->win, get_color(CLR_LEGEND)); for (i = w->help_screen_top; i < max_lines && i < help_lines; i++) { xwaddstr (w->win, help[i]); waddch (w->win, '\n'); } if (i != help_lines) { wattrset (w->win, get_color(CLR_MESSAGE)); xmvwaddstr (w->win, LINES-5, COLS/2 - (sizeof("...MORE...")-1)/2, "...MORE..."); } } static void main_win_draw_lyrics_screen (const struct main_win *w) { int i; int max_lines; int height, width; lists_t_strs *lyrics_array; assert (w != NULL); assert (w->in_lyrics); max_lines = w->lyrics_screen_top + LINES - 6; werase (w->win); wbkgd (w->win, get_color(CLR_BACKGROUND)); wmove (w->win, 0, 0); if (w->lyrics_screen_top != 0) { wattrset (w->win, get_color(CLR_MESSAGE)); xmvwaddstr (w->win, 0, COLS/2 - (sizeof("...MORE...")-1)/2, "...MORE..."); } wmove (w->win, 1, 0); wattrset (w->win, get_color(CLR_LEGEND)); getmaxyx (w->win, height, width); lyrics_array = lyrics_format (height, width); for (i = w->lyrics_screen_top; i < max_lines && i < lists_strs_size (lyrics_array); i++) xwaddstr (w->win, lists_strs_at (lyrics_array, i)); if (i != lists_strs_size (lyrics_array)) { wattrset (w->win, get_color(CLR_MESSAGE)); xmvwaddstr (w->win, LINES-5, COLS/2 - (sizeof("...MORE...")-1)/2, "...MORE..."); } lists_strs_free (lyrics_array); } static void main_win_draw (struct main_win *w) { size_t ix; if (w->in_help) main_win_draw_help_screen (w); else if (w->in_lyrics) main_win_draw_lyrics_screen (w); else if (w->too_small) main_win_draw_too_small_screen (w); else { werase (w->win); /* Draw all visible menus. Draw the selected menu last. */ for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) if (w->menus[ix].visible && ix != (size_t)w->selected_menu) side_menu_draw (&w->menus[ix], 0); side_menu_draw (&w->menus[w->selected_menu], 1); } } static enum side_menu_type iface_to_side_menu (const enum iface_menu iface_menu) { switch (iface_menu) { case IFACE_MENU_PLIST: return MENU_PLAYLIST; case IFACE_MENU_DIR: return MENU_DIR; default: abort (); /* BUG */ } } static void main_win_set_dir_content (struct main_win *w, const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists) { struct side_menu *m; assert (w != NULL); m = find_side_menu (w, iface_to_side_menu(iface_menu)); side_menu_make_list_content (m, files, dirs, playlists, iface_menu == IFACE_MENU_DIR); if (w->curr_file) side_menu_mark_file (m, w->curr_file); main_win_draw (w); } static void main_win_set_title (struct main_win *w, const enum side_menu_type type, const char *title) { struct side_menu *m; assert (w != NULL); assert (title != NULL); m = find_side_menu (w, type); side_menu_set_title (m, title); main_win_draw (w); } static void main_win_update_dir_content (struct main_win *w, const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists) { struct side_menu *m; struct side_menu_state ms; assert (w != NULL); m = find_side_menu (w, iface_menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST); side_menu_get_state (m, &ms); side_menu_make_list_content (m, files, dirs, playlists, 1); side_menu_set_state (m, &ms); if (w->curr_file) side_menu_mark_file (m, w->curr_file); main_win_draw (w); } static void main_win_switch_to (struct main_win *w, const enum side_menu_type menu) { size_t ix; assert (w != NULL); if (w->selected_menu == 2) /* if the themes menu is selected */ side_menu_destroy (&w->menus[2]); for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) if (w->menus[ix].type == menu) { w->selected_menu = ix; break; } assert (ix < ARRAY_SIZE(w->menus)); main_win_draw (w); } static void main_win_switch_to_help (struct main_win *w) { assert (w != NULL); w->in_help = 1; main_win_draw (w); } static void main_win_switch_to_lyrics (struct main_win *w) { assert (w != NULL); w->in_lyrics = 1; main_win_draw (w); } static void main_win_create_themes_menu (struct main_win *w) { struct window_params p; assert (w != NULL); p.x = 0; p.y = 0; p.width = COLS; p.height = LINES - 4; side_menu_init (&w->menus[2], MENU_THEMES, w->win, &p); side_menu_set_title (&w->menus[2], "Themes"); } static void main_win_menu_cmd (struct main_win *w, const enum key_cmd cmd) { assert (w != NULL); side_menu_cmd (&w->menus[w->selected_menu], cmd); main_win_draw (w); } static enum file_type main_win_curritem_get_type (const struct main_win *w) { assert (w != NULL); return side_menu_curritem_get_type (&w->menus[w->selected_menu]); } static char *main_win_get_curr_file (const struct main_win *w) { assert (w != NULL); return side_menu_get_curr_file (&w->menus[w->selected_menu]); } static int main_win_in_dir_menu (const struct main_win *w) { assert (w != NULL); return w->menus[w->selected_menu].type == MENU_DIR; } static int main_win_in_help (const struct main_win *w) { assert (w != NULL); return w->in_help; } static int main_win_in_lyrics (const struct main_win *w) { assert (w != NULL); return w->in_lyrics; } static int main_win_in_plist_menu (const struct main_win *w) { assert (w != NULL); return w->menus[w->selected_menu].type == MENU_PLAYLIST; } static int main_win_in_theme_menu (const struct main_win *w) { assert (w != NULL); return w->menus[w->selected_menu].type == MENU_THEMES; } static void main_win_set_curr_item_title (struct main_win *w, const char *title) { assert (w != NULL); assert (title != NULL); side_menu_set_curr_item_title (&w->menus[w->selected_menu], title); main_win_draw (w); } /* Update item title and time on all menus where it's present. */ static void main_win_update_item (struct main_win *w, const enum iface_menu iface_menu, const struct plist *plist, const int n) { struct side_menu *m; assert (w != NULL); assert (plist != NULL); assert (LIMIT(n, plist->num)); m = find_side_menu (w, iface_to_side_menu(iface_menu)); if (side_menu_update_item(m, plist, n)) main_win_draw (w); } /* Mark the played file on all lists of files or unmark it when file is NULL. */ static void main_win_set_played_file (struct main_win *w, const char *file) { size_t ix; assert (w != NULL); if (w->curr_file) free (w->curr_file); w->curr_file = xstrdup (file); for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) { struct side_menu *m = &w->menus[ix]; if (m->visible && (m->type == MENU_DIR || m->type == MENU_PLAYLIST)) { side_menu_unmark_file (m); if (file) side_menu_mark_file (m, file); } } main_win_draw (w); } static int main_win_menu_filter (struct main_win *w, const char *pattern) { int num; assert (w != NULL); assert (pattern != NULL); num = side_menu_filter (&w->menus[w->selected_menu], pattern); if (num) main_win_draw (w); return num; } static void main_win_clear_filter_menu (struct main_win *w) { assert (w != NULL); side_menu_use_main (&w->menus[w->selected_menu]); main_win_draw (w); } static void main_win_set_plist_time (struct main_win *w, const int time, const int time_for_all) { struct side_menu *m; assert (w != NULL); m = find_side_menu (w, MENU_PLAYLIST); side_menu_set_plist_time (m, time, time_for_all); } static void main_win_add_to_plist (struct main_win *w, const struct plist *plist, const int num) { struct side_menu *m; int need_redraw; assert (plist != NULL); m = find_side_menu (w, MENU_PLAYLIST); need_redraw = side_menu_add_plist_item (m, plist, num); if (w->curr_file) side_menu_mark_file (m, w->curr_file); if (need_redraw) main_win_draw (w); } static void main_win_add_file (struct main_win *w, const char *file, const char *title, const enum file_type type) { assert (w != NULL); assert (file != NULL); assert (title != NULL); side_menu_add_file (&w->menus[w->selected_menu], file, title, type); main_win_draw (w); } static int main_win_get_files_time (const struct main_win *w, const enum iface_menu menu) { struct side_menu *m; assert (w != NULL); m = find_side_menu ((struct main_win *)w, iface_to_side_menu(menu)); return side_menu_get_files_time (m); } static int main_win_is_time_for_all (const struct main_win *w, const enum iface_menu menu) { struct side_menu *m; assert (w != NULL); m = find_side_menu ((struct main_win *)w, iface_to_side_menu(menu)); return side_menu_is_time_for_all (m); } static int main_win_get_curr_files_time (const struct main_win *w) { assert (w != NULL); return side_menu_get_files_time (&w->menus[w->selected_menu]); } static int main_win_is_curr_time_for_all (const struct main_win *w) { assert (w != NULL); return side_menu_is_time_for_all (&w->menus[w->selected_menu]); } static void main_win_handle_help_key (struct main_win *w, const struct iface_key *k) { int help_lines; assert (w != NULL); assert (w->in_help); get_keys_help (&help_lines); if ((k->type == IFACE_KEY_FUNCTION && ( k->key.func == KEY_DOWN || k->key.func == KEY_NPAGE)) || (k->key.ucs == '\n')) { if (w->help_screen_top + LINES - 5 <= help_lines) w->help_screen_top++; } else { if (k->type == IFACE_KEY_FUNCTION && (k->key.func == KEY_UP || k->key.func == KEY_PPAGE)) { if (w->help_screen_top > 0) w->help_screen_top--; } else if (k->key.func != KEY_RESIZE) w->in_help = 0; } main_win_draw (w); } static void main_win_handle_lyrics_key (struct main_win *w, const struct iface_key *k) { int height, width; lists_t_strs *lyrics_array; assert (w != NULL); assert (w->in_lyrics); if ((k->type == IFACE_KEY_FUNCTION && ( k->key.func == KEY_DOWN || k->key.func == KEY_NPAGE)) || (k->key.ucs == '\n')) { getmaxyx (w->win, height, width); lyrics_array = lyrics_format (height, width); if (w->lyrics_screen_top + LINES - 5 <= lists_strs_size (lyrics_array)) w->lyrics_screen_top++; lists_strs_free (lyrics_array); } else { if (k->type == IFACE_KEY_FUNCTION && (k->key.func == KEY_UP || k->key.func == KEY_PPAGE)) { if (w->lyrics_screen_top > 0) w->lyrics_screen_top--; } else if (k->key.func != KEY_RESIZE) w->in_lyrics = 0; } main_win_draw (w); } static void main_win_swap_plist_items (struct main_win *w, const char *file1, const char *file2) { struct side_menu *m; assert (w != NULL); assert (file1 != NULL); assert (file2 != NULL); m = find_side_menu (w, MENU_PLAYLIST); side_menu_swap_items (m, file1, file2); main_win_draw (w); } static void main_win_use_layout (struct main_win *w, lists_t_strs *layout_fmt) { struct main_win_layout l; bool rc ASSERT_ONLY; assert (w != NULL); assert (layout_fmt != NULL); w->layout_fmt = layout_fmt; rc = parse_layout (&l, layout_fmt); assert (rc); side_menu_resize (&w->menus[0], &l.menus[0]); side_menu_resize (&w->menus[1], &l.menus[1]); main_win_draw (w); } static void validate_layouts () { struct main_win_layout l; lists_t_strs *layout_fmt; layout_fmt = options_get_list ("Layout1"); if (lists_strs_empty (layout_fmt) || !parse_layout(&l, layout_fmt)) interface_fatal ("Layout1 is malformed!"); layout_fmt = options_get_list ("Layout2"); if (!lists_strs_empty (layout_fmt) && !parse_layout(&l, layout_fmt)) interface_fatal ("Layout2 is malformed!"); layout_fmt = options_get_list ("Layout3"); if (!lists_strs_empty (layout_fmt) && !parse_layout(&l, layout_fmt)) interface_fatal ("Layout3 is malformed!"); } /* Handle terminal size change. */ static void main_win_resize (struct main_win *w) { struct main_win_layout l; bool rc ASSERT_ONLY; assert (w != NULL); keypad (w->win, TRUE); wresize (w->win, LINES - 4, COLS); werase (w->win); rc = parse_layout (&l, w->layout_fmt); assert (rc); side_menu_resize (&w->menus[0], &l.menus[0]); side_menu_resize (&w->menus[1], &l.menus[1]); if (w->menus[2].visible) { /* Themes menu */ struct window_params p; p.x = 0; p.y = 0; p.width = COLS; p.height = LINES - 4; side_menu_resize (&w->menus[2], &p); } main_win_draw (w); } static void main_win_make_visible (struct main_win *w, const enum side_menu_type type, const char *file) { struct side_menu *m; assert (w != NULL); assert (file != NULL); m = find_side_menu (w, type); side_menu_make_visible (m, file); main_win_draw (w); } static void main_win_update_show_time (struct main_win *w) { size_t ix; assert (w != NULL); for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) { struct side_menu *m = &w->menus[ix]; if (m->visible && (m->type == MENU_DIR || m->type == MENU_PLAYLIST)) side_menu_update_show_time (&w->menus[ix]); } main_win_draw (w); } static void main_win_select_file (struct main_win *w, const char *file) { assert (w != NULL); assert (file != NULL); side_menu_select_file (&w->menus[w->selected_menu], file); main_win_draw (w); } static void main_win_update_show_format (struct main_win *w) { size_t ix; assert (w != NULL); for (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) { struct side_menu *m = &w->menus[ix]; if (m->visible && (m->type == MENU_DIR || m->type == MENU_PLAYLIST)) side_menu_update_show_format (&w->menus[ix]); } main_win_draw (w); } static void main_win_del_plist_item (struct main_win *w, const char *file) { struct side_menu *m; assert (w != NULL); assert (file != NULL); m = find_side_menu (w, MENU_PLAYLIST); side_menu_del_item (m, file); main_win_draw (w); } static void main_win_clear_plist (struct main_win *w) { struct side_menu *m; assert (w != NULL); m = find_side_menu (w, MENU_PLAYLIST); side_menu_clear (m); main_win_draw (w); } /* Write to a file and log but otherwise ignore any error. */ static void soft_write (int fd, const void *buf, size_t count) { ssize_t rc; rc = write (fd, buf, count); if (rc < 0) log_errno ("write() failed", errno); } /* Set the has_xterm variable. */ static void detect_term () { char *term; term = getenv ("TERM"); if (term) { lists_t_strs *xterms; xterms = options_get_list ("XTerms"); has_xterm = lists_strs_exists (xterms, term); } } static void xterm_set_title (const int state, const char *title) { if (has_xterm && options_get_bool("SetXtermTitle")) { soft_write (1, "\033]0;", sizeof("\033]0;")-1); soft_write (1, "MOC ", sizeof("MOC ")-1); switch (state) { case STATE_PLAY: soft_write (1, "[play]", sizeof("[play]")-1); break; case STATE_STOP: soft_write (1, "[stop]", sizeof("[stop]")-1); break; case STATE_PAUSE: soft_write (1, "[pause]", sizeof("[pause]")-1); break; } if (title) { soft_write (1, " - ", sizeof(" - ")-1); if (options_get_bool ("NonUTFXterm")) { char *iconv_title = xterm_iconv_str (title); soft_write (1, iconv_title, strlen(iconv_title)); free (iconv_title); } else { soft_write (1, title, strlen(title)); } } soft_write (1, "\007", 1); } } static void xterm_clear_title () { if (has_xterm && options_get_bool("SetXtermTitle")) soft_write (1, "\033]2;\007", sizeof("\033]2;\007")-1); } /* Set the has_screen variable. */ static void detect_screen () { char *term, *window; term = getenv ("TERM"); window = getenv ("WINDOW"); if (term && window && isdigit (*window)) { lists_t_strs *screen_terms; screen_terms = options_get_list ("ScreenTerms"); has_screen = lists_strs_exists (screen_terms, term); } } #define SCREEN_TITLE_START "\033k" #define SCREEN_TITLE_END "\033\\" static void screen_set_title (const int state, const char *title) { if (has_screen && options_get_bool("SetScreenTitle")) { soft_write (1, SCREEN_TITLE_START, sizeof(SCREEN_TITLE_START)-1); soft_write (1, "MOC ", sizeof("MOC ")-1); switch (state) { case STATE_PLAY: soft_write (1, "[play]", sizeof("[play]")-1); break; case STATE_STOP: soft_write (1, "[stop]", sizeof("[stop]")-1); break; case STATE_PAUSE: soft_write (1, "[pause]", sizeof("[pause]")-1); break; } if (title) { soft_write (1, " - ", sizeof(" - ")-1); soft_write (1, title, strlen(title)); } soft_write (1, SCREEN_TITLE_END, sizeof(SCREEN_TITLE_END)-1); } } static void screen_clear_title () { if (has_screen && options_get_bool("SetScreenTitle")) { soft_write (1, SCREEN_TITLE_START, sizeof(SCREEN_TITLE_START)-1); soft_write (1, SCREEN_TITLE_END, sizeof(SCREEN_TITLE_END)-1); } } /* Based on ASCIILines option initialize line characters with curses lines or * ASCII characters. */ static void init_lines () { if (options_get_bool("ASCIILines")) { lines.vert = '|'; lines.horiz = '-'; lines.ulcorn = '+'; lines.urcorn = '+'; lines.llcorn = '+'; lines.lrcorn = '+'; lines.rtee = '|'; lines.ltee = '|'; } else { lines.vert = ACS_VLINE; lines.horiz = ACS_HLINE; lines.ulcorn = ACS_ULCORNER; lines.urcorn = ACS_URCORNER; lines.llcorn = ACS_LLCORNER; lines.lrcorn = ACS_LRCORNER; lines.rtee = ACS_RTEE; lines.ltee = ACS_LTEE; } } /* End the program if the terminal is too small. */ static void check_term_size (struct main_win *mw, struct info_win *iw) { mw->too_small = iw->too_small = COLS < 59 || LINES < 7; } /* Update the title with the current fill. */ static void bar_update_title (struct bar *b) { char pct[8]; assert (b != NULL); assert (b->show_val); if (!b->show_pct) sprintf (b->title, "%*s", b->width, b->orig_title); else { sprintf (b->title, "%*s", b->width - 7, b->orig_title); strcpy (pct, " 100% "); /* The snprintf(3) below can never output 310 bytes! */ SUPPRESS_FORMAT_TRUNCATION_WARNING if (b->filled < 99.99) snprintf (pct, sizeof (pct), " %02.0f%% ", b->filled); UNSUPPRESS_FORMAT_TRUNCATION_WARNING strncpy (&b->title[b->width - 7], pct, strlen (pct)); } } static void bar_set_title (struct bar *b, const char *title) { assert (b != NULL); assert (b->show_val); assert (title != NULL); assert (strlen(title) < sizeof(b->title) - 5); strncpy (b->orig_title, title, b->width); b->orig_title[b->width] = 0; bar_update_title (b); } static void bar_init (struct bar *b, const int width, const char *title, const int show_val, const int show_pct, const int fill_color, const int empty_color) { assert (b != NULL); assert (width > 5 && width < ssizeof(b->title)); assert (title != NULL || !show_val); b->width = width; b->filled = 0.0; b->show_val = show_val; b->show_pct = show_pct; b->fill_color = fill_color; b->empty_color = empty_color; if (show_val) { b->orig_title = xmalloc (b->width + 1); bar_set_title (b, title); } else { b->orig_title = NULL; memset (b->title, ' ', b->width); b->title[b->width] = 0; } } static void bar_draw (const struct bar *b, WINDOW *win, const int pos_x, const int pos_y) { int fill_chars; /* how many chars are "filled" */ assert (b != NULL); assert (win != NULL); assert (LIMIT(pos_x, COLS - b->width)); assert (LIMIT(pos_y, LINES)); fill_chars = b->filled * b->width / 100.0; wattrset (win, b->fill_color); xmvwaddnstr (win, pos_y, pos_x, b->title, fill_chars); wattrset (win, b->empty_color); xwaddstr (win, b->title + fill_chars); } static void bar_set_fill (struct bar *b, const double fill) { assert (b != NULL); assert (fill >= 0.0); b->filled = MIN(fill, 100.0); if (b->show_val) bar_update_title (b); } static void bar_resize (struct bar *b, const int width) { assert (b != NULL); assert (width > 5 && width < ssizeof(b->title)); if (b->show_val && b->width < width) { char *new_title = xmalloc (width + 1); strcpy (new_title, b->orig_title); free (b->orig_title); b->orig_title = new_title; } b->width = width; if (b->show_val) bar_update_title (b); else { memset (b->title, ' ', b->width); b->title[b->width] = 0; } } static struct queued_message *queued_message_create (enum message_type type) { struct queued_message *result; result = (struct queued_message *) xmalloc (sizeof (struct queued_message)); result->next = NULL; result->type = type; result->msg = NULL; result->prompt = NULL; result->timeout = 0; result->callback = NULL; result->data = NULL; return result; } static void queued_message_destroy (struct queued_message *msg) { assert (msg != NULL); if (msg->msg) free (msg->msg); if (msg->prompt) free (msg->prompt); free (msg); } static void set_startup_message (struct info_win *w) { assert (w != NULL); w->current_message = queued_message_create (NORMAL_MSG); w->current_message->msg = xstrdup (STARTUP_MESSAGE); w->current_message->timeout = time (NULL); w->current_message->timeout += options_get_int ("MessageLingerTime"); if (is_help_still_h ()) { struct queued_message *msg; msg = queued_message_create (NORMAL_MSG); msg->msg = xstrdup ("Press 'h' for the list of commands."); msg->timeout = options_get_int ("MessageLingerTime"); w->queued_message_head = msg; w->queued_message_tail = msg; w->queued_message_total = 1; } } static void info_win_init (struct info_win *w) { assert (w != NULL); w->win = newwin (4, COLS, LINES - 4, 0); wbkgd (w->win, get_color(CLR_BACKGROUND)); w->queued_message_head = NULL; w->queued_message_tail = NULL; w->queued_message_total = 0; w->queued_message_errors = 0; w->too_small = 0; w->state_stereo = false; w->state_shuffle = false; w->state_repeat = false; w->state_next = false; w->state_play = STATE_STOP; w->state_net = false; w->bitrate = -1; w->rate = -1; w->files_in_queue = 0; w->curr_time = -1; w->total_time = -1; w->block_start = -1; w->block_end = -1; w->title = NULL; w->status_msg[0] = 0; w->in_entry = 0; entry_history_init (&w->urls_history); entry_history_init (&w->dirs_history); entry_history_init (&w->user_history); set_startup_message (w); bar_init (&w->mixer_bar, 20, "", 1, 1, get_color(CLR_MIXER_BAR_FILL), get_color(CLR_MIXER_BAR_EMPTY)); bar_init (&w->time_bar, COLS - 4, "", 1, options_get_bool("ShowTimePercent") ? 1 : 0, get_color(CLR_TIME_BAR_FILL), get_color(CLR_TIME_BAR_EMPTY)); } static void info_win_destroy (struct info_win *w) { assert (w != NULL); if (w->win) delwin (w->win); if (w->in_entry) entry_destroy (&w->entry); entry_history_clear (&w->urls_history); entry_history_clear (&w->dirs_history); entry_history_clear (&w->user_history); } /* Set the cursor position in the right place if needed. */ static void info_win_update_curs (const struct info_win *w) { assert (w != NULL); if (w->in_entry && !w->too_small) entry_draw (&w->entry, w->win, 1, 0); } static void info_win_set_mixer_name (struct info_win *w, const char *name) { assert (w != NULL); assert (name != NULL); bar_set_title (&w->mixer_bar, name); if (!w->in_entry && !w->too_small) { bar_draw (&w->mixer_bar, w->win, COLS - 37, 0); info_win_update_curs (w); } } static void info_win_draw_status (const struct info_win *w) { assert (w != NULL); if (!w->in_entry && !w->too_small) { wattrset (w->win, get_color(CLR_STATUS)); wmove (w->win, 0, 6); xwprintw (w->win, "%-*s", (int) sizeof(w->status_msg) - 1, w->status_msg); info_win_update_curs (w); } } static void info_win_set_status (struct info_win *w, const char *msg) { assert (w != NULL); assert (msg != NULL); assert (strlen(msg) < sizeof(w->status_msg)); strcpy (w->status_msg, msg); info_win_draw_status (w); } static void info_win_draw_files_in_queue (const struct info_win *w) { const int hstart = 5 + sizeof(w->status_msg) + 2; assert (w != NULL); if(!w->in_entry && !w->too_small) { if (w->files_in_queue) { wattrset (w->win, get_color(CLR_STATUS)); mvwaddch (w->win, 0, hstart, lines.rtee); xwprintw (w->win, "Q:%3d", w->files_in_queue); waddch (w->win, lines.ltee); } else { wattrset (w->win, get_color(CLR_FRAME)); mvwhline (w->win, 0, hstart, lines.horiz, 9); } } info_win_update_curs (w); } static void info_win_set_files_in_queue (struct info_win *w, const int num) { assert (w != NULL); assert (num >= 0); w->files_in_queue = num; info_win_draw_files_in_queue (w); } static void info_win_draw_state (const struct info_win *w) { const char *state_symbol; assert (w != NULL); switch (w->state_play) { case STATE_PLAY: state_symbol = " >"; break; case STATE_STOP: state_symbol = "[]"; break; case STATE_PAUSE: state_symbol = "||"; break; default: abort (); /* BUG */ } if (!w->too_small) { wattrset (w->win, get_color(CLR_STATE)); xmvwaddstr (w->win, 1, 1, state_symbol); } info_win_update_curs (w); } /* Draw the title or the message (informative or error). */ static void info_win_draw_title (const struct info_win *w) { assert (w != NULL); if (!w->too_small) { clear_area (w->win, 4, 1, COLS - 5, 1); if (w->current_message && w->current_message->msg && w->current_message->timeout >= time (NULL)) { wattrset (w->win, w->current_message->type == ERROR_MSG ? get_color (CLR_ERROR) : get_color (CLR_MESSAGE)); xmvwaddnstr (w->win, 1, 4, w->current_message->msg, COLS - 5); } else { wattrset (w->win, get_color (CLR_TITLE)); xmvwaddnstr (w->win, 1, 4, w->title ? w->title : "", COLS - 5); } } info_win_update_curs (w); } static void info_win_set_state (struct info_win *w, const int state) { assert (w != NULL); assert (state == STATE_PLAY || state == STATE_STOP || state == STATE_PAUSE); w->state_play = state; xterm_set_title (state, w->title); screen_set_title (state, w->title); info_win_draw_state (w); } static void info_win_draw_time (const struct info_win *w) { char time_str[32]; assert (w != NULL); if (!w->too_small) { /* current time */ sec_to_min (time_str, w->curr_time != -1 ? w->curr_time : 0); wattrset (w->win, get_color(CLR_TIME_CURRENT)); xmvwaddstr (w->win, 2, 1, time_str); /* time left */ if (w->total_time > 0 && w->curr_time >= 0 && w->total_time >= w->curr_time) { sec_to_min (time_str, w->total_time - w->curr_time); wmove (w->win, 2, 7); wattrset (w->win, get_color(CLR_TIME_LEFT)); xwaddstr (w->win, time_str); } else xmvwaddstr (w->win, 2, 7, " "); /* total time */ sec_to_min (time_str, w->total_time != -1 ? w->total_time : 0); wmove (w->win, 2, 14); wattrset (w->win, get_color(CLR_TIME_TOTAL)); xwaddstr (w->win, time_str); bar_draw (&w->time_bar, w->win, 2, 3); } info_win_update_curs (w); } static void info_win_draw_block (const struct info_win *w) { assert (w != NULL); if (!w->too_small) bar_draw (&w->time_bar, w->win, 2, 3); info_win_update_curs (w); } static void info_win_set_curr_time (struct info_win *w, const int time) { assert (w != NULL); assert (time >= -1); w->curr_time = time; if (w->total_time > 0 && w->curr_time >= 0) bar_set_fill (&w->time_bar, w->curr_time * 100.0 / w->total_time); else bar_set_fill (&w->time_bar, 0.0); info_win_draw_time (w); } static void info_win_set_total_time (struct info_win *w, const int time) { assert (w != NULL); assert (time >= -1); w->total_time = time; if (w->total_time > 0 && w->curr_time >= 0) bar_set_fill (&w->time_bar, w->curr_time * 100.0 / w->total_time); else bar_set_fill (&w->time_bar, 0.0); info_win_draw_time (w); } static void info_win_set_block_title (struct info_win *w) { assert (w != NULL); if (w->total_time == -1) { bar_set_title (&w->time_bar, ""); } else if (w->block_start == -1 || w->block_end == -1) { bar_set_title (&w->time_bar, ""); } else if (w->block_start == 0 && w->block_end == w->total_time) { bar_set_title (&w->time_bar, ""); } else { int start_pos, end_pos; char *new_title, *decorators; start_pos = w->block_start * w->time_bar.width / w->total_time; if (w->block_end < w->total_time) end_pos = w->block_end * w->time_bar.width / w->total_time; else end_pos = w->time_bar.width - 1; new_title = xmalloc(w->time_bar.width + 1); memset(new_title, ' ', w->time_bar.width); decorators = options_get_str ("BlockDecorators"); if (start_pos == end_pos) { new_title[start_pos] = decorators[1]; } else { new_title[start_pos] = decorators[0]; new_title[end_pos] = decorators[2]; } new_title[w->time_bar.width] = 0x00; bar_set_title (&w->time_bar, new_title); } } static void info_win_set_block (struct info_win *w, const int block_start, const int block_end) { assert (w != NULL); assert (block_start == -1 || RANGE(0, block_start, w->total_time)); assert (block_end == -1 || RANGE(0, block_end, w->total_time)); info_win.block_start = block_start; info_win.block_end = block_end; info_win_set_block_title (w); info_win_draw_block (w); } static void info_win_set_played_title (struct info_win *w, const char *title) { assert (w != NULL); if (!w->title && !title) return; if (w->title && title && !strcmp(w->title, title)) return; if (w->title) free (w->title); w->title = xstrdup (title); xterm_set_title (w->state_play, title); screen_set_title (w->state_play, title); info_win_draw_title (w); } static void info_win_draw_rate (const struct info_win *w) { assert (w != NULL); wattrset (w->win, get_color(CLR_SOUND_PARAMS)); wmove (w->win, 2, 22); if (w->rate != -1) xwprintw (w->win, "%3d", w->rate); else xwaddstr (w->win, " "); } static void info_win_draw_bitrate (const struct info_win *w) { assert (w != NULL); if (!w->too_small) { wattrset (w->win, get_color(CLR_SOUND_PARAMS)); wmove (w->win, 2, 29); if (w->bitrate != -1) xwprintw (w->win, "%4d", MIN(w->bitrate, 9999)); else xwaddstr (w->win, " "); } info_win_update_curs (w); } static void info_win_set_bitrate (struct info_win *w, const int bitrate) { assert (w != NULL); assert (bitrate >= -1); w->bitrate = bitrate > 0 ? bitrate : -1; info_win_draw_bitrate (w); } static void info_win_set_rate (struct info_win *w, const int rate) { assert (w != NULL); assert (rate >= -1); w->rate = rate > 0 ? rate : -1; info_win_draw_rate (w); } static void info_win_set_mixer_value (struct info_win *w, const int value) { assert (w != NULL); bar_set_fill (&w->mixer_bar, (double) value); if (!w->in_entry && !w->too_small) bar_draw (&w->mixer_bar, w->win, COLS - 37, 0); } /* Draw a switch that is turned on or off in form of [TITLE]. */ static void info_win_draw_switch (const struct info_win *w, const int posx, const int posy, const char *title, const bool value) { assert (w != NULL); assert (title != NULL); if (!w->too_small) { wattrset (w->win, get_color( value ? CLR_INFO_ENABLED : CLR_INFO_DISABLED)); wmove (w->win, posy, posx); xwprintw (w->win, "[%s]", title); } info_win_update_curs (w); } static void info_win_draw_options_state (const struct info_win *w) { assert (w != NULL); info_win_draw_switch (w, 38, 2, "STEREO", w->state_stereo); info_win_draw_switch (w, 47, 2, "NET", w->state_net); info_win_draw_switch (w, 53, 2, "SHUFFLE", w->state_shuffle); info_win_draw_switch (w, 63, 2, "REPEAT", w->state_repeat); info_win_draw_switch (w, 72, 2, "NEXT", w->state_next); } static void info_win_make_entry (struct info_win *w, const enum entry_type type) { struct entry_history *history; const char *prompt; assert (w != NULL); assert (!w->in_entry); prompt = NULL; switch (type) { case ENTRY_GO_DIR: history = &w->dirs_history; break; case ENTRY_GO_URL: history = &w->urls_history; break; case ENTRY_ADD_URL: history = &w->urls_history; break; case ENTRY_USER_QUERY: history = &w->user_history; prompt = w->current_message->prompt; break; default: history = NULL; } entry_init (&w->entry, type, COLS - 4, history, prompt); w->in_entry = 1; curs_set (1); entry_draw (&w->entry, w->win, 1, 0); } /* Display the next queued message. */ static void info_win_display_msg (struct info_win *w) { int msg_changed; assert (w != NULL); msg_changed = 0; if (w->current_message && time (NULL) > w->current_message->timeout) { w->callback = w->current_message->callback; w->data = w->current_message->data; queued_message_destroy (w->current_message); w->current_message = NULL; msg_changed = 1; } if (!w->current_message && w->queued_message_head && !w->in_entry) { w->current_message = w->queued_message_head; w->queued_message_head = w->current_message->next; w->current_message->next = NULL; if (!w->queued_message_head) w->queued_message_tail = NULL; w->queued_message_total -= 1; if (w->current_message->type == ERROR_MSG) w->queued_message_errors -= 1; if (msg_changed && w->current_message->msg && options_get_bool ("PrefixQueuedMessages")) { char *msg, *decorator; int len; msg = w->current_message->msg; decorator = options_get_str ("ErrorMessagesQueued"); len = strlen (msg) + strlen (decorator) + 10; w->current_message->msg = (char *) xmalloc (len); snprintf (w->current_message->msg, len, "(%d%s) %s", w->queued_message_total, (w->queued_message_errors ? decorator : ""), msg); w->current_message->msg[len - 1] = 0x00; free (msg); } if (w->current_message->type == QUERY_MSG) { info_win_make_entry (w, ENTRY_USER_QUERY); w->current_message->timeout = 86400; } w->current_message->timeout += time (NULL); msg_changed = 1; } if (msg_changed) info_win_draw_title (w); } /* Force the next queued message to be displayed. */ static void info_win_disable_msg (struct info_win *w) { assert (w != NULL); if (w->current_message) { w->current_message->timeout = 0; info_win_display_msg (w); } } /* Clear all queued messages. */ static void info_win_clear_msg (struct info_win *w) { assert (w != NULL); while (w->queued_message_head) { struct queued_message *this_msg; this_msg = w->queued_message_head; w->queued_message_head = this_msg->next; queued_message_destroy (this_msg); } w->queued_message_total = 0; w->queued_message_errors = 0; w->queued_message_tail = NULL; if (w->current_message) { queued_message_destroy (w->current_message); w->current_message = NULL; } } /* Queue a new message for display. */ static void info_win_msg (struct info_win *w, const char *msg, enum message_type msg_type, const char *prompt, t_user_reply_callback *callback, void *data) { struct queued_message *this_msg; assert (w != NULL); assert (msg != NULL || prompt != NULL); this_msg = queued_message_create (msg_type); if (msg) this_msg->msg = xstrdup (msg); if (prompt) this_msg->prompt = xstrdup (prompt); this_msg->timeout = options_get_int ("MessageLingerTime"); this_msg->callback = callback; this_msg->data = data; if (w->queued_message_head) { w->queued_message_tail->next = this_msg; w->queued_message_tail = this_msg; } else { w->queued_message_head = this_msg; w->queued_message_tail = this_msg; } w->queued_message_total += 1; if (msg_type == ERROR_MSG) w->queued_message_errors += 1; info_win_display_msg (w); } static void iface_win_user_reply (struct info_win *w, const char *reply) { assert (w != NULL); if (w->callback) w->callback (reply, w->data); } static void info_win_user_history_add (struct info_win *w, const char *text) { assert (w != NULL); entry_history_add (&w->user_history, text); } static void info_win_set_channels (struct info_win *w, const int channels) { assert (w != NULL); assert (channels == 1 || channels == 2); w->state_stereo = (channels == 2); info_win_draw_options_state (w); } static int info_win_in_entry (const struct info_win *w) { assert (w != NULL); return w->in_entry; } static enum entry_type info_win_get_entry_type (const struct info_win *w) { assert (w != NULL); assert (w->in_entry); return entry_get_type (&w->entry); } static void info_win_set_option_state (struct info_win *w, const char *name, const bool value) { assert (w != NULL); assert (name != NULL); if (!strcasecmp(name, "Shuffle")) w->state_shuffle = value; else if (!strcasecmp(name, "Repeat")) w->state_repeat = value; else if (!strcasecmp(name, "AutoNext")) w->state_next = value; else if (!strcasecmp(name, "Net")) w->state_net = value; else abort (); info_win_draw_options_state (w); } /* Convert time in second to min:sec text format(for total time in playlist). * 'buff' must be 48 chars long. */ static void sec_to_min_plist (char *buff, const int seconds) { assert (seconds >= 0); if (seconds < 999 * 60 * 60 - 1) { /* the time is less than 999 * 60 minutes */ int hour, min, sec; hour = seconds / 3600; min = (seconds / 60) % 60; sec = seconds % 60; snprintf (buff, 48, "%03d:%02d:%02d", hour, min, sec); } else strcpy (buff, "!!!!!!!!!"); } static void info_win_draw_files_time (const struct info_win *w) { assert (w != NULL); if (!w->in_entry && !w->too_small) { char buf[48]; sec_to_min_plist (buf, w->plist_time); wmove (w->win, 0, COLS - 12); wattrset (w->win, get_color(CLR_PLIST_TIME)); waddch (w->win, w->plist_time_for_all ? ' ' : '>'); xwaddstr (w->win, buf); info_win_update_curs (w); } } /* Set the total time for files in the displayed menu. If time_for_all * has a non zero value, the time is for all files. */ static void info_win_set_files_time (struct info_win *w, const int time, const int time_for_all) { assert (w != NULL); w->plist_time = time; w->plist_time_for_all = time_for_all; info_win_draw_files_time (w); } /* Update the message timeout, redraw the window if needed. */ static void info_win_tick (struct info_win *w) { info_win_display_msg (w); } /* Draw static elements of info_win: frames, legend etc. */ static void info_win_draw_static_elements (const struct info_win *w) { assert (w != NULL); if (!w->too_small) { /* window frame */ wattrset (w->win, get_color(CLR_FRAME)); wborder (w->win, lines.vert, lines.vert, lines.horiz, lines.horiz, lines.ltee, lines.rtee, lines.llcorn, lines.lrcorn); /* mixer frame */ mvwaddch (w->win, 0, COLS - 38, lines.rtee); mvwaddch (w->win, 0, COLS - 17, lines.ltee); /* playlist time frame */ mvwaddch (w->win, 0, COLS - 13, lines.rtee); mvwaddch (w->win, 0, COLS - 2, lines.ltee); /* total time frames */ wattrset (w->win, get_color(CLR_TIME_TOTAL_FRAMES)); mvwaddch (w->win, 2, 13, '['); mvwaddch (w->win, 2, 19, ']'); /* time bar frame */ wattrset (w->win, get_color(CLR_FRAME)); mvwaddch (w->win, 3, COLS - 2, lines.ltee); mvwaddch (w->win, 3, 1, lines.rtee); /* status line frame */ mvwaddch (w->win, 0, 5, lines.rtee); mvwaddch (w->win, 0, 5 + sizeof(w->status_msg), lines.ltee); /* rate and bitrate units */ wmove (w->win, 2, 25); wattrset (w->win, get_color(CLR_LEGEND)); xwaddstr (w->win, "kHz kbps"); } info_win_update_curs (w); } static void info_win_draw (const struct info_win *w) { assert (w != NULL); if (!w->too_small) { info_win_draw_static_elements (w); info_win_draw_state (w); info_win_draw_time (w); info_win_draw_block (w); info_win_draw_title (w); info_win_draw_options_state (w); info_win_draw_status (w); info_win_draw_files_in_queue (w); info_win_draw_files_time (w); info_win_draw_bitrate (w); info_win_draw_rate (w); if (w->in_entry) entry_draw (&w->entry, w->win, 1, 0); else bar_draw (&w->mixer_bar, w->win, COLS - 37, 0); bar_draw (&w->time_bar, w->win, 2, 3); } info_win_update_curs (w); } static void info_win_entry_disable (struct info_win *w) { assert (w != NULL); assert (w->in_entry); entry_destroy (&w->entry); w->in_entry = 0; if (!options_get_bool("UseCursorSelection")) curs_set (0); info_win_draw (w); } /* Handle a key while in entry. main_win is used to update the menu (filter * only matching items) when ENTRY_SEARCH is used. */ static void info_win_entry_handle_key (struct info_win *iw, struct main_win *mw, const struct iface_key *k) { enum key_cmd cmd; enum entry_type type; assert (iw != NULL); assert (mw != NULL); assert (iw->in_entry); cmd = get_key_cmd (CON_ENTRY, k); type = entry_get_type (&iw->entry); if (type == ENTRY_SEARCH) { char *text; if (k->type == IFACE_KEY_CHAR) { if (iswprint(k->key.ucs)) { entry_add_char (&iw->entry, k->key.ucs); text = entry_get_text (&iw->entry); if (!main_win_menu_filter(mw, text)) entry_back_space (&iw->entry); free (text); } } else if (k->key.func == KEY_BACKSPACE) { entry_back_space (&iw->entry); text = entry_get_text (&iw->entry); main_win_menu_filter (mw, text); free (text); } else if (cmd == KEY_CMD_CANCEL) { main_win_clear_filter_menu (mw); info_win_entry_disable (iw); } else { enum key_cmd cmd = get_key_cmd (CON_MENU, k); if (cmd == KEY_CMD_MENU_UP || cmd == KEY_CMD_MENU_DOWN || cmd == KEY_CMD_MENU_NPAGE || cmd == KEY_CMD_MENU_PPAGE || cmd == KEY_CMD_MENU_FIRST || cmd == KEY_CMD_MENU_LAST) main_win_menu_cmd (mw, cmd); } } else { if (k->type == IFACE_KEY_CHAR) { if (iswprint(k->key.ucs)) entry_add_char (&iw->entry, k->key.ucs); } else if (k->key.func == KEY_LEFT) entry_curs_left (&iw->entry); else if (k->key.func == KEY_RIGHT) entry_curs_right (&iw->entry); else if (k->key.func == KEY_BACKSPACE) entry_back_space (&iw->entry); else if (k->key.func == KEY_DC) entry_del_char (&iw->entry); else if (k->key.func == KEY_HOME) entry_home (&iw->entry); else if (k->key.func == KEY_END) entry_end (&iw->entry); else if (cmd == KEY_CMD_CANCEL) { info_win_entry_disable (iw); if (type == ENTRY_USER_QUERY) iface_user_reply (NULL); } else if ((type == ENTRY_GO_DIR || type == ENTRY_GO_URL || type == ENTRY_ADD_URL || type == ENTRY_USER_QUERY) && cmd != KEY_CMD_WRONG) { if (cmd == KEY_CMD_HISTORY_UP) entry_set_history_up (&iw->entry); else if (cmd == KEY_CMD_HISTORY_DOWN) entry_set_history_down (&iw->entry); else if (cmd == KEY_CMD_DELETE_START) entry_del_to_start (&iw->entry); else if (cmd == KEY_CMD_DELETE_END) entry_del_to_end (&iw->entry); } } if (iw->in_entry) /* the entry could be disabled above */ entry_draw (&iw->entry, iw->win, 1, 0); } static void info_win_entry_set_text (struct info_win *w, const char *text) { assert (w != NULL); assert (text != NULL); assert (w->in_entry); entry_set_text (&w->entry, text); entry_draw (&w->entry, w->win, 1, 0); } static char *info_win_entry_get_text (const struct info_win *w) { assert (w != NULL); assert (w->in_entry); return entry_get_text (&w->entry); } static void info_win_entry_history_add (struct info_win *w) { assert (w != NULL); assert (w->in_entry); entry_add_text_to_history (&w->entry); } static void info_win_entry_set_file (struct info_win *w, const char *file) { assert (w != NULL); assert (w->in_entry); assert (file != NULL); entry_set_file (&w->entry, file); } static char *info_win_entry_get_file (const struct info_win *w) { assert (w != NULL); assert (w->in_entry); return entry_get_file (&w->entry); } /* Handle terminal size change. */ static void info_win_resize (struct info_win *w) { assert (w != NULL); keypad (w->win, TRUE); wresize (w->win, 4, COLS); mvwin (w->win, LINES - 4, 0); werase (w->win); bar_resize (&w->mixer_bar, 20); bar_resize (&w->time_bar, COLS - 4); info_win_set_block_title (w); if (w->in_entry) entry_resize (&w->entry, COLS - 4); info_win_draw (w); } void windows_init () { if (getenv ("ESCDELAY") == NULL) { #ifdef HAVE_SET_ESCDELAY set_escdelay (25); #else setenv ("ESCDELAY", "25", 0); #endif } utf8_init (); if (!initscr ()) fatal ("Can't initialize terminal!"); screen_initialized = 1; validate_layouts (); cbreak (); noecho (); if (!options_get_bool ("UseCursorSelection")) curs_set (0); use_default_colors (); detect_term (); detect_screen (); start_color (); theme_init (has_xterm); init_lines (); main_win_init (&main_win, options_get_list ("Layout1")); info_win_init (&info_win); check_term_size (&main_win, &info_win); main_win_draw (&main_win); info_win_draw (&info_win); wnoutrefresh (main_win.win); wnoutrefresh (info_win.win); doupdate (); iface_initialized = 1; } void windows_reset () { if (screen_initialized) { screen_initialized = 0; /* endwin() sometimes fails on X-terminals when we get SIGCHLD * at this moment. Double invocation seems to solve this. */ if (endwin () == ERR && endwin () == ERR) logit ("endwin() failed!"); /* Make sure that the next line after we exit will be "clear". */ printf ("\n"); fflush (stdout); } } void windows_end () { if (iface_initialized) { iface_initialized = 0; main_win_destroy (&main_win); info_win_clear_msg (&info_win); info_win_destroy (&info_win); xterm_clear_title (); screen_clear_title (); utf8_cleanup (); } windows_reset (); lyrics_cleanup (); } static void iface_refresh_screen () { /* We must do it in proper order to get the right cursor position. */ if (iface_in_entry ()) { wnoutrefresh (main_win.win); wnoutrefresh (info_win.win); } else { wnoutrefresh (info_win.win); wnoutrefresh (main_win.win); } doupdate (); } /* Set state of the options displayed in the information window. */ void iface_set_option_state (const char *name, const bool value) { assert (name != NULL); info_win_set_option_state (&info_win, name, value); iface_refresh_screen (); } /* Set the mixer name. */ void iface_set_mixer_name (const char *name) { assert (name != NULL); info_win_set_mixer_name (&info_win, name); iface_refresh_screen (); } /* Set the status message in the info window. */ void iface_set_status (const char *msg) { assert (msg != NULL); if (iface_initialized) { info_win_set_status (&info_win, msg); iface_refresh_screen (); } } /* Set the number of files in song queue in the info window */ void iface_set_files_in_queue (const int num) { assert (num >= 0); if (iface_initialized) { info_win_set_files_in_queue (&info_win, num); iface_refresh_screen (); } } static void iface_show_num_files (const int num) { char str[20]; snprintf (str, sizeof(str), "Files: %d", num); iface_set_status (str); } /* Change the content of the directory menu to these files, directories, and * playlists. */ void iface_set_dir_content (const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists) { main_win_set_dir_content (&main_win, iface_menu, files, dirs, playlists); info_win_set_files_time (&info_win, main_win_get_files_time(&main_win, iface_menu), main_win_is_time_for_all(&main_win, iface_menu)); iface_show_num_files (plist_count(files) + (dirs ? lists_strs_size (dirs) : 0) + (playlists ? lists_strs_size (playlists) : 0)); iface_refresh_screen (); } /* Refreshes all menu structs with updated theme attributes. */ void iface_update_attrs () { size_t ix; info_win.mixer_bar.fill_color = get_color (CLR_MIXER_BAR_FILL); info_win.mixer_bar.empty_color = get_color (CLR_MIXER_BAR_EMPTY); info_win.time_bar.fill_color = get_color (CLR_TIME_BAR_FILL); info_win.time_bar.empty_color = get_color (CLR_TIME_BAR_EMPTY); for (ix = 0; ix < ARRAY_SIZE(main_win.menus); ix += 1) { int item_num; struct side_menu *m = &main_win.menus[ix]; struct menu *menu = m->menu.list.main; struct menu_item *mi; if (m->type == MENU_DIR || m->type == MENU_PLAYLIST) { menu_set_info_attr_normal (menu, get_color (CLR_MENU_ITEM_INFO)); menu_set_info_attr_sel (menu, get_color (CLR_MENU_ITEM_INFO_SELECTED)); menu_set_info_attr_marked (menu, get_color (CLR_MENU_ITEM_INFO_MARKED)); menu_set_info_attr_sel_marked (menu, get_color (CLR_MENU_ITEM_INFO_MARKED_SELECTED)); for (mi = menu->items, item_num = 0; mi && item_num < menu->nitems; mi = mi->next, item_num += 1) { if (mi->type == F_DIR) { menu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_DIR)); menu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_DIR_SELECTED)); } else if (mi->type == F_PLAYLIST) { menu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_PLAYLIST)); menu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_PLAYLIST_SELECTED)); } else { menu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_FILE)); menu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_FILE_SELECTED)); } } } else { menu_set_info_attr_normal (menu, get_color (CLR_MENU_ITEM_FILE)); menu_set_info_attr_sel (menu, get_color (CLR_MENU_ITEM_FILE_SELECTED)); for (mi = menu->items, item_num = 0; mi && item_num < menu->nitems; mi = mi->next, item_num += 1) { menu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_FILE)); menu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_FILE_SELECTED)); } } } } void iface_update_theme_selection (const char *file) { /* menus[2] is theme menu. */ assert (main_win.menus[2].menu.list.main->selected); menu_setcurritem_file (main_win.menus[2].menu.list.main, file); } /* Like iface_set_dir_content(), but before replacing the menu content, save * the menu state (selected file, view position) and restore it after making * a new menu. */ void iface_update_dir_content (const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists) { main_win_update_dir_content (&main_win, iface_menu, files, dirs, playlists); info_win_set_files_time (&info_win, main_win_get_files_time(&main_win, iface_menu), main_win_is_time_for_all(&main_win, iface_menu)); iface_show_num_files (plist_count(files) + (dirs ? lists_strs_size (dirs) : 0) + (playlists ? lists_strs_size (playlists) : 0)); iface_refresh_screen (); } /* Update item title and time in the menu. */ void iface_update_item (const enum iface_menu menu, const struct plist *plist, const int n) { assert (plist != NULL); main_win_update_item (&main_win, menu, plist, n); info_win_set_files_time (&info_win, main_win_get_curr_files_time(&main_win), main_win_is_curr_time_for_all(&main_win)); iface_refresh_screen (); } /* Change the current item in the directory menu to this item. */ void iface_set_curr_item_title (const char *title) { assert (title != NULL); main_win_set_curr_item_title (&main_win, title); iface_refresh_screen (); } /* Set the title for the directory menu. */ void iface_set_title (const enum iface_menu menu, const char *title) { assert (title != NULL); if (options_get_bool ("FileNamesIconv")) { char *conv_title = NULL; conv_title = files_iconv_str (title); main_win_set_title (&main_win, menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST, conv_title); free (conv_title); } else { main_win_set_title (&main_win, menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST, title); } iface_refresh_screen (); } /* Get the char code from the user with meta flag set if necessary. */ void iface_get_key (struct iface_key *k) { wint_t ch; ch = wgetch (main_win.win); if (ch == (wint_t)ERR) interface_fatal ("wgetch() failed!"); /* Handle keypad ENTER as newline. */ if (ch == KEY_ENTER) ch = '\n'; if (ch < 32 && ch != '\n' && ch != '\t' && ch != KEY_ESCAPE) { /* Unprintable, generally control sequences */ k->type = IFACE_KEY_FUNCTION; k->key.func = ch; } else if (ch == 0x7f) { /* Workaround for backspace on many terminals */ k->type = IFACE_KEY_FUNCTION; k->key.func = KEY_BACKSPACE; } else if (ch < 255) { /* Regular char */ int meta; #ifdef HAVE_NCURSESW ungetch (ch); if (wget_wch(main_win.win, &ch) == ERR) interface_fatal ("wget_wch() failed!"); #endif /* Recognize meta sequences */ if (ch == KEY_ESCAPE) { meta = wgetch (main_win.win); if (meta != ERR) ch = meta | META_KEY_FLAG; k->type = IFACE_KEY_FUNCTION; k->key.func = ch; } else { k->type = IFACE_KEY_CHAR; k->key.ucs = ch; } } else { k->type = IFACE_KEY_FUNCTION; k->key.func = ch; } } /* Return a non zero value if the key is not a real key - KEY_RESIZE. */ int iface_key_is_resize (const struct iface_key *k) { return k->type == IFACE_KEY_FUNCTION && k->key.func == KEY_RESIZE; } /* Handle a key command for the menu. */ void iface_menu_key (const enum key_cmd cmd) { main_win_menu_cmd (&main_win, cmd); iface_refresh_screen (); } /* Get the type of the currently selected item. */ enum file_type iface_curritem_get_type () { return main_win_curritem_get_type (&main_win); } /* Return a non zero value if a directory menu is currently selected. */ int iface_in_dir_menu () { return main_win_in_dir_menu (&main_win); } /* Return a non zero value if the playlist menu is currently selected. */ int iface_in_plist_menu () { return main_win_in_plist_menu (&main_win); } /* Return a non zero value if the theme menu is currently selected. */ int iface_in_theme_menu () { return main_win_in_theme_menu (&main_win); } /* Return the currently selected file (malloc()ed) or NULL if the menu is * empty. */ char *iface_get_curr_file () { return main_win_get_curr_file (&main_win); } /* Set the current time of playing. */ void iface_set_curr_time (const int time) { info_win_set_curr_time (&info_win, time); iface_refresh_screen (); } /* Set the total time for the currently played file. */ void iface_set_total_time (const int time) { info_win_set_total_time (&info_win, time); info_win_set_block (&info_win, -1, -1); iface_refresh_screen (); } /* Set the start and end marks for the currently played file. */ void iface_set_block (const int block_start, const int block_end) { info_win_set_block (&info_win, block_start, block_end); iface_refresh_screen (); } /* Set the state (STATE_(PLAY|STOP|PAUSE)). */ void iface_set_state (const int state) { info_win_set_state (&info_win, state); iface_refresh_screen (); } /* Set the bitrate (in kbps). 0 or -1 means no bitrate information. */ void iface_set_bitrate (const int bitrate) { assert (bitrate >= -1); info_win_set_bitrate (&info_win, bitrate); iface_refresh_screen (); } /* Set the rate (in kHz). 0 or -1 means no rate information. */ void iface_set_rate (const int rate) { assert (rate >= -1); info_win_set_rate (&info_win, rate); iface_refresh_screen (); } /* Set the number of channels. */ void iface_set_channels (const int channels) { assert (channels == 1 || channels == 2); info_win_set_channels (&info_win, channels); iface_refresh_screen (); } /* Set the currently played file. If file is NULL, nothing is played. */ void iface_set_played_file (const char *file) { main_win_set_played_file (&main_win, file); if (!file) { info_win_set_played_title (&info_win, NULL); info_win_set_bitrate (&info_win, -1); info_win_set_rate (&info_win, -1); info_win_set_curr_time (&info_win, -1); info_win_set_total_time (&info_win, -1); info_win_set_block (&info_win, -1, -1); info_win_set_option_state (&info_win, "Net", 0); } else if (is_url(file)) { info_win_set_option_state (&info_win, "Net", 1); } iface_refresh_screen (); } /* Set the title for the currently played file. */ void iface_set_played_file_title (const char *title) { assert (title != NULL); info_win_set_played_title (&info_win, title); iface_refresh_screen (); } /* Update timeouts, refresh the screen if needed. This should be called at * least once a second. */ void iface_tick () { info_win_tick (&info_win); iface_refresh_screen (); } void iface_set_mixer_value (const int value) { assert (value >= 0); info_win_set_mixer_value (&info_win, value); iface_refresh_screen (); } /* Switch to the playlist menu. */ void iface_switch_to_plist () { main_win_switch_to (&main_win, MENU_PLAYLIST); info_win_set_files_time (&info_win, main_win_get_curr_files_time(&main_win), main_win_is_curr_time_for_all(&main_win)); iface_refresh_screen (); } /* Switch to the directory menu. */ void iface_switch_to_dir () { main_win_switch_to (&main_win, MENU_DIR); info_win_set_files_time (&info_win, main_win_get_curr_files_time(&main_win), main_win_is_curr_time_for_all(&main_win)); iface_refresh_screen (); } /* Add the item from the playlist to the playlist menu. */ void iface_add_to_plist (const struct plist *plist, const int num) { assert (plist != NULL); main_win_add_to_plist (&main_win, plist, num); info_win_set_files_time (&info_win, main_win_get_curr_files_time(&main_win), main_win_is_curr_time_for_all(&main_win)); iface_show_num_files (plist_count(plist)); iface_refresh_screen (); } /* Display an error message. */ void iface_error (const char *msg) { if (iface_initialized) { info_win_msg (&info_win, msg, ERROR_MSG, NULL, NULL, NULL); iface_refresh_screen (); } else fprintf (stderr, "ERROR: %s", msg); } /* Handle screen resizing. */ void iface_resize () { endwin (); refresh (); check_term_size (&main_win, &info_win); validate_layouts (); main_win_resize (&main_win); info_win_resize (&info_win); iface_refresh_screen (); } void iface_refresh () { wclear (main_win.win); wclear (info_win.win); main_win_draw (&main_win); info_win_draw (&info_win); iface_refresh_screen (); } void iface_update_show_time () { main_win_update_show_time (&main_win); iface_refresh_screen (); } void iface_update_show_format () { main_win_update_show_format (&main_win); iface_refresh_screen (); } void iface_clear_plist () { main_win_clear_plist (&main_win); iface_refresh_screen (); } void iface_del_plist_item (const char *file) { assert (file != NULL); main_win_del_plist_item (&main_win, file); info_win_set_files_time (&info_win, main_win_get_curr_files_time(&main_win), main_win_is_curr_time_for_all(&main_win)); iface_refresh_screen (); } void iface_make_entry (const enum entry_type type) { info_win_make_entry (&info_win, type); iface_refresh_screen (); } enum entry_type iface_get_entry_type () { return info_win_get_entry_type (&info_win); } int iface_in_entry () { return info_win_in_entry (&info_win); } void iface_entry_handle_key (const struct iface_key *k) { info_win_entry_handle_key (&info_win, &main_win, k); iface_refresh_screen (); } void iface_entry_set_text (const char *text) { assert (text != NULL); info_win_entry_set_text (&info_win, text); iface_refresh_screen (); } /* Get text from the entry. Returned memory is malloc()ed. */ char *iface_entry_get_text () { return info_win_entry_get_text (&info_win); } void iface_entry_history_add () { info_win_entry_history_add (&info_win); } void iface_entry_disable () { if (iface_get_entry_type() == ENTRY_SEARCH) main_win_clear_filter_menu (&main_win); info_win_entry_disable (&info_win); iface_refresh_screen (); } void iface_entry_set_file (const char *file) { assert (file != NULL); info_win_entry_set_file (&info_win, file); } /* Returned memory is malloc()ed. */ char *iface_entry_get_file () { return info_win_entry_get_file (&info_win); } void iface_message (const char *msg) { assert (msg != NULL); info_win_msg (&info_win, msg, NORMAL_MSG, NULL, NULL, NULL); iface_refresh_screen (); } void iface_disable_message () { info_win_disable_msg (&info_win); iface_refresh_screen (); } void iface_user_query (const char *msg, const char *prompt, t_user_reply_callback *callback, void *data) { assert (prompt != NULL); info_win_msg (&info_win, msg, QUERY_MSG, prompt, callback, data); iface_refresh_screen (); } void iface_user_reply (const char *reply) { info_win_disable_msg (&info_win); iface_win_user_reply (&info_win, reply); } void iface_user_history_add (const char *text) { info_win_user_history_add (&info_win, text); } void iface_plist_set_total_time (const int time, const int for_all_files) { if (iface_in_plist_menu()) info_win_set_files_time (&info_win, time, for_all_files); main_win_set_plist_time (&main_win, time, for_all_files); iface_refresh_screen (); } void iface_select_file (const char *file) { assert (file != NULL); main_win_select_file (&main_win, file); iface_refresh_screen (); } int iface_in_help () { return main_win_in_help (&main_win); } void iface_switch_to_help () { main_win_switch_to_help (&main_win); iface_refresh_screen (); } void iface_handle_help_key (const struct iface_key *k) { main_win_handle_help_key (&main_win, k); iface_refresh_screen (); } int iface_in_lyrics () { return main_win_in_lyrics (&main_win); } void iface_switch_to_lyrics () { main_win_switch_to_lyrics (&main_win); iface_refresh_screen (); } void iface_handle_lyrics_key (const struct iface_key *k) { main_win_handle_lyrics_key (&main_win, k); iface_refresh_screen (); } void iface_toggle_layout () { static int curr_layout = 1; char layout_option[32]; lists_t_strs *layout_fmt; if (++curr_layout > 3) curr_layout = 1; sprintf (layout_option, "Layout%d", curr_layout); layout_fmt = options_get_list (layout_option); if (lists_strs_empty (layout_fmt)) { curr_layout = 1; layout_fmt = options_get_list ("Layout1"); } main_win_use_layout (&main_win, layout_fmt); iface_refresh_screen (); } void iface_toggle_percent () { info_win.time_bar.show_pct = !info_win.time_bar.show_pct; bar_update_title (&info_win.time_bar); info_win_draw_block (&info_win); iface_refresh_screen (); } void iface_swap_plist_items (const char *file1, const char *file2) { main_win_swap_plist_items (&main_win, file1, file2); iface_refresh_screen (); } /* Make sure that this file in this menu is visible. */ void iface_make_visible (const enum iface_menu menu, const char *file) { assert (file != NULL); main_win_make_visible (&main_win, menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST, file); iface_refresh_screen (); } void iface_switch_to_theme_menu () { main_win_create_themes_menu (&main_win); main_win_switch_to (&main_win, MENU_THEMES); iface_refresh_screen (); } /* Add a file to the current menu. */ void iface_add_file (const char *file, const char *title, const enum file_type type) { assert (file != NULL); assert (file != NULL); main_win_add_file (&main_win, file, title, type); iface_refresh_screen (); } /* Temporary exit the interface (ncurses mode). */ void iface_temporary_exit () { endwin (); } /* Restore the interface after iface_temporary_exit(). */ void iface_restore () { iface_refresh (); if (!options_get_bool("UseCursorSelection")) curs_set (0); } void iface_load_lyrics (const char *file) { lyrics_cleanup (); lyrics_autoload (file); main_win.lyrics_screen_top = 0; main_win_draw (&main_win); } static void update_queue_position (struct plist *playlist, struct plist *dir_list, const char *file, const int pos) { int i; assert (file != NULL); assert (pos >= 0); if (playlist && (i = plist_find_fname(playlist, file)) != -1) { playlist->items[i].queue_pos = pos; main_win_update_item (&main_win, IFACE_MENU_PLIST, playlist, i); } if (dir_list && (i = plist_find_fname(dir_list, file)) != -1) { dir_list->items[i].queue_pos = pos; main_win_update_item (&main_win, IFACE_MENU_DIR, dir_list, i); } } /* Update queue positions in the playlist and directory menus. Only those items * which are in the queue (and whose position has therefore changed are * updated. One exception is the item which was deleted from the queue -- * this one can be passed as the deleted_file parameter */ void iface_update_queue_positions (const struct plist *queue, struct plist *playlist, struct plist *dir_list, const char *deleted_file) { int i; int pos = 1; assert (queue != NULL); for (i = 0; i < queue->num; i++) { if (!plist_deleted(queue,i)) { update_queue_position (playlist, dir_list, queue->items[i].file, pos); pos++; } } if (deleted_file) update_queue_position (playlist, dir_list, deleted_file, 0); iface_refresh_screen (); } /* Clear the queue -- zero the queue positions in playlist and directory * menus. */ void iface_clear_queue_positions (const struct plist *queue, struct plist *playlist, struct plist *dir_list) { int i; assert (queue != NULL); assert (playlist != NULL); assert (dir_list != NULL); for (i = 0; i < queue->num; i++) { if (!plist_deleted(queue,i)) { update_queue_position (playlist, dir_list, queue->items[i].file, 0); } } iface_refresh_screen (); } void iface_update_queue_position_last (const struct plist *queue, struct plist *playlist, struct plist *dir_list) { int i; int pos; assert (queue != NULL); i = plist_last (queue); pos = plist_get_position (queue, i); update_queue_position (playlist, dir_list, queue->items[i].file, pos); iface_refresh_screen (); } moc-2.6.0~svn-r3005/interface_elements.h0000664000076400000500000001142013012456773017142 0ustar riesebiesrc#ifndef INTERFACE_ELEMENTS_H #define INTERFACE_ELEMENTS_H #if defined HAVE_NCURSESW_CURSES_H # include #elif defined HAVE_NCURSESW_H # include #elif defined HAVE_NCURSES_CURSES_H # include #elif defined HAVE_NCURSES_H # include #elif defined HAVE_CURSES_H # include #endif #include #include #include "lists.h" #include "files.h" #include "keys.h" #ifdef __cplusplus extern "C" { #endif /* Interface's menus */ enum iface_menu { IFACE_MENU_PLIST, IFACE_MENU_DIR }; typedef void t_user_reply_callback (const char *reply, void *data); enum entry_type { ENTRY_SEARCH, ENTRY_PLIST_SAVE, ENTRY_GO_DIR, ENTRY_GO_URL, ENTRY_ADD_URL, ENTRY_PLIST_OVERWRITE, ENTRY_USER_QUERY }; struct iface_key { /* Type of the key */ enum { IFACE_KEY_CHAR, /* Regular char */ IFACE_KEY_FUNCTION /* Function key (arrow, F12, etc.) */ } type; union { wchar_t ucs; /* IFACE_KEY_CHAR */ int func; /* IFACE_KEY_FUNCTION */ } key; }; void windows_init (); void windows_reset (); void windows_end (); void iface_set_option_state (const char *name, const bool value); void iface_set_mixer_name (const char *name); void iface_set_status (const char *msg); void iface_set_dir_content (const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists); void iface_update_dir_content (const enum iface_menu iface_menu, const struct plist *files, const lists_t_strs *dirs, const lists_t_strs *playlists); void iface_set_curr_item_title (const char *title); void iface_get_key (struct iface_key *k); int iface_key_is_resize (const struct iface_key *k); void iface_menu_key (const enum key_cmd cmd); enum file_type iface_curritem_get_type (); int iface_in_dir_menu (); int iface_in_plist_menu (); int iface_in_theme_menu (); char *iface_get_curr_file (); void iface_update_item (const enum iface_menu menu, const struct plist *plist, const int n); void iface_set_curr_time (const int time); void iface_set_total_time (const int time); void iface_set_block (const int start_time, const int end_time); void iface_set_state (const int state); void iface_set_bitrate (const int bitrate); void iface_set_rate (const int rate); void iface_set_channels (const int channels); void iface_set_played_file (const char *file); void iface_set_played_file_title (const char *title); void iface_set_mixer_value (const int value); void iface_set_files_in_queue (const int num); void iface_tick (); void iface_switch_to_plist (); void iface_switch_to_dir (); void iface_add_to_plist (const struct plist *plist, const int num); void iface_error (const char *msg); void iface_resize (); void iface_refresh (); void iface_update_show_time (); void iface_update_show_format (); void iface_clear_plist (); void iface_del_plist_item (const char *file); enum entry_type iface_get_entry_type (); int iface_in_entry (); void iface_make_entry (const enum entry_type type); void iface_entry_handle_key (const struct iface_key *k); void iface_entry_set_text (const char *text); char *iface_entry_get_text (); void iface_entry_history_add (); void iface_entry_disable (); void iface_entry_set_file (const char *file); char *iface_entry_get_file (); void iface_message (const char *msg); void iface_disable_message (); void iface_user_query (const char *msg, const char *prompt, t_user_reply_callback *callback, void *data); void iface_user_reply (const char *reply); void iface_user_history_add (const char *text); void iface_plist_set_total_time (const int time, const int for_all_files); void iface_set_title (const enum iface_menu menu, const char *title); void iface_select_file (const char *file); int iface_in_help (); void iface_switch_to_help (); void iface_handle_help_key (const struct iface_key *k); int iface_in_lyrics (); void iface_switch_to_lyrics (); void iface_handle_lyrics_key (const struct iface_key *k); void iface_toggle_layout (); void iface_toggle_percent (); void iface_swap_plist_items (const char *file1, const char *file2); void iface_make_visible (const enum iface_menu menu, const char *file); void iface_switch_to_theme_menu (); void iface_add_file (const char *file, const char *title, const enum file_type type); void iface_temporary_exit (); void iface_restore (); void iface_load_lyrics (const char *file); void iface_update_queue_positions (const struct plist *queue, struct plist *playlist, struct plist *dir_list, const char *deleted_file); void iface_clear_queue_positions (const struct plist *queue, struct plist *playlist, struct plist *dir_list); void iface_update_queue_position_last (const struct plist *queue, struct plist *playlist, struct plist *dir_list); void iface_update_attrs (); void iface_update_theme_selection (const char *file); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/io.c0000664000076400000500000004250612747016150013714 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ /* TODO: * - handle SIGBUS (mmap() read error) */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MMAP # include #endif /*#define DEBUG*/ #include "common.h" #include "log.h" #include "io.h" #include "options.h" #include "files.h" #ifdef HAVE_CURL # include "io_curl.h" #endif #ifdef HAVE_CURL # define CURL_ONLY #else # define CURL_ONLY ATTR_UNUSED #endif #ifdef HAVE_MMAP static void *io_mmap_file (const struct io_stream *s) { void *result = NULL; do { if (s->size < 1 || (uint64_t)s->size > SIZE_MAX) { logit ("File size unsuitable for mmap()"); break; } const size_t sz = (size_t)s->size; result = mmap (0, sz, PROT_READ, MAP_SHARED, s->fd, 0); if (result == MAP_FAILED) { log_errno ("mmap() failed", errno); result = NULL; break; } logit ("mmap()ed %zu bytes", sz); } while (0); return result; } #endif #ifdef HAVE_MMAP static ssize_t io_read_mmap (struct io_stream *s, const int dont_move, void *buf, size_t count) { struct stat file_stat; size_t to_read; assert (s->mem != NULL); if (fstat (s->fd, &file_stat) == -1) { log_errno ("fstat() failed", errno); return -1; } if (s->size != file_stat.st_size) { logit ("File size has changed"); if (munmap (s->mem, (size_t)s->size)) { log_errno ("munmap() failed", errno); return -1; } s->size = file_stat.st_size; s->mem = io_mmap_file (s); if (!s->mem) return -1; if (s->mem_pos > s->size) logit ("File shrunk"); } if (s->mem_pos >= s->size) return 0; to_read = MIN(count, (size_t) (s->size - s->mem_pos)); memcpy (buf, (char *)s->mem + s->mem_pos, to_read); if (!dont_move) s->mem_pos += to_read; return to_read; } #endif static ssize_t io_read_fd (struct io_stream *s, const int dont_move, void *buf, size_t count) { ssize_t res; res = read (s->fd, buf, count); if (res < 0) return -1; if (dont_move && lseek(s->fd, -res, SEEK_CUR) < 0) return -1; return res; } /* Read the data from the stream resource. If dont_move was set, the stream * position is unchanged. */ static ssize_t io_internal_read (struct io_stream *s, const int dont_move, char *buf, size_t count) { ssize_t res = 0; assert (s != NULL); assert (buf != NULL); switch (s->source) { case IO_SOURCE_FD: res = io_read_fd (s, dont_move, buf, count); break; #ifdef HAVE_MMAP case IO_SOURCE_MMAP: res = io_read_mmap (s, dont_move, buf, count); break; #endif #ifdef HAVE_CURL case IO_SOURCE_CURL: if (dont_move) fatal ("You can't peek data directly from CURL!"); res = io_curl_read (s, buf, count); break; #endif default: fatal ("Unknown io_stream->source: %d", s->source); } return res; } #ifdef HAVE_MMAP static off_t io_seek_mmap (struct io_stream *s, const off_t where) { return (s->mem_pos = where); } #endif static off_t io_seek_fd (struct io_stream *s, const off_t where) { return lseek (s->fd, where, SEEK_SET); } static off_t io_seek_buffered (struct io_stream *s, const off_t where) { off_t res = -1; assert (s->source != IO_SOURCE_CURL); logit ("Seeking..."); switch (s->source) { case IO_SOURCE_FD: res = io_seek_fd (s, where); break; #ifdef HAVE_MMAP case IO_SOURCE_MMAP: res = io_seek_mmap (s, where); break; #endif default: fatal ("Unknown io_stream->source: %d", s->source); } LOCK (s->buf_mtx); fifo_buf_clear (s->buf); pthread_cond_signal (&s->buf_free_cond); s->after_seek = 1; s->eof = 0; UNLOCK (s->buf_mtx); return res; } static off_t io_seek_unbuffered (struct io_stream *s, const off_t where) { off_t res = -1; assert (s->source != IO_SOURCE_CURL); switch (s->source) { #ifdef HAVE_MMAP case IO_SOURCE_MMAP: res = io_seek_mmap (s, where); break; #endif case IO_SOURCE_FD: res = io_seek_fd (s, where); break; default: fatal ("Unknown io_stream->source: %d", s->source); } return res; } off_t io_seek (struct io_stream *s, off_t offset, int whence) { off_t res, new_pos = 0; assert (s != NULL); assert (s->opened); if (s->source == IO_SOURCE_CURL || !io_ok(s)) return -1; LOCK (s->io_mtx); switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos = s->pos + offset; break; case SEEK_END: new_pos = s->size + offset; break; default: fatal ("Bad whence value: %d", whence); } new_pos = CLAMP(0, new_pos, s->size); if (s->buffered) res = io_seek_buffered (s, new_pos); else res = io_seek_unbuffered (s, new_pos); if (res != -1) s->pos = res; UNLOCK (s->io_mtx); if (res != -1) debug ("Seek to: %"PRId64, res); else logit ("Seek error"); return res; } /* Wake up the IO reading thread. */ static void io_wake_up (struct io_stream *s CURL_ONLY) { #ifdef HAVE_CURL if (s->source == IO_SOURCE_CURL) io_curl_wake_up (s); #endif } /* Abort an IO operation from another thread. */ void io_abort (struct io_stream *s) { assert (s != NULL); if (s->buffered && !s->stop_read_thread) { logit ("Aborting..."); LOCK (s->buf_mtx); s->stop_read_thread = 1; io_wake_up (s); pthread_cond_broadcast (&s->buf_fill_cond); pthread_cond_broadcast (&s->buf_free_cond); UNLOCK (s->buf_mtx); logit ("done"); } } /* Close the stream and free all resources associated with it. */ void io_close (struct io_stream *s) { int rc; assert (s != NULL); logit ("Closing stream..."); if (s->opened) { if (s->buffered) { io_abort (s); logit ("Waiting for io_read_thread()..."); pthread_join (s->read_thread, NULL); logit ("IO read thread exited"); } switch (s->source) { case IO_SOURCE_FD: close (s->fd); break; #ifdef HAVE_MMAP case IO_SOURCE_MMAP: if (s->mem && munmap (s->mem, (size_t)s->size)) log_errno ("munmap() failed", errno); close (s->fd); break; #endif #ifdef HAVE_CURL case IO_SOURCE_CURL: io_curl_close (s); break; #endif default: fatal ("Unknown io_stream->source: %d", s->source); } s->opened = 0; if (s->buffered) { fifo_buf_free (s->buf); s->buf = NULL; rc = pthread_cond_destroy (&s->buf_free_cond); if (rc != 0) log_errno ("Destroying buf_free_cond failed", rc); rc = pthread_cond_destroy (&s->buf_fill_cond); if (rc != 0) log_errno ("Destroying buf_fill_cond failed", rc); } if (s->metadata.title) free (s->metadata.title); if (s->metadata.url) free (s->metadata.url); } rc = pthread_mutex_destroy (&s->buf_mtx); if (rc != 0) log_errno ("Destroying buf_mtx failed", rc); rc = pthread_mutex_destroy (&s->io_mtx); if (rc != 0) log_errno ("Destroying io_mtx failed", rc); rc = pthread_mutex_destroy (&s->metadata.mtx); if (rc != 0) log_errno ("Destroying metadata.mtx failed", rc); if (s->strerror) free (s->strerror); free (s); logit ("done"); } static void *io_read_thread (void *data) { struct io_stream *s = (struct io_stream *)data; logit ("IO read thread created"); while (!s->stop_read_thread) { char read_buf[8096]; int read_buf_fill = 0; int read_buf_pos = 0; LOCK (s->io_mtx); debug ("Reading..."); LOCK (s->buf_mtx); s->after_seek = 0; UNLOCK (s->buf_mtx); read_buf_fill = io_internal_read (s, 0, read_buf, sizeof(read_buf)); UNLOCK (s->io_mtx); if (read_buf_fill > 0) debug ("Read %d bytes", read_buf_fill); LOCK (s->buf_mtx); if (s->stop_read_thread) { UNLOCK (s->buf_mtx); break; } if (read_buf_fill < 0) { s->errno_val = errno; s->read_error = 1; logit ("Exiting due to read error."); pthread_cond_broadcast (&s->buf_fill_cond); UNLOCK (s->buf_mtx); break; } if (read_buf_fill == 0) { s->eof = 1; debug ("EOF, waiting"); pthread_cond_broadcast (&s->buf_fill_cond); pthread_cond_wait (&s->buf_free_cond, &s->buf_mtx); debug ("Got signal"); UNLOCK (s->buf_mtx); continue; } s->eof = 0; while (read_buf_pos < read_buf_fill && !s->after_seek) { size_t put; debug ("Buffer fill: %zu", fifo_buf_get_fill (s->buf)); put = fifo_buf_put (s->buf, read_buf + read_buf_pos, read_buf_fill - read_buf_pos); if (s->stop_read_thread) break; if (put > 0) { debug ("Put %zu bytes into the buffer", put); if (s->buf_fill_callback) { UNLOCK (s->buf_mtx); s->buf_fill_callback (s, fifo_buf_get_fill (s->buf), fifo_buf_get_size (s->buf), s->buf_fill_callback_data); LOCK (s->buf_mtx); } pthread_cond_broadcast (&s->buf_fill_cond); read_buf_pos += put; continue; } debug ("The buffer is full, waiting."); pthread_cond_wait (&s->buf_free_cond, &s->buf_mtx); debug ("Some space in the buffer was freed"); } UNLOCK (s->buf_mtx); } if (s->stop_read_thread) logit ("Stop request"); logit ("Exiting IO read thread"); return NULL; } static void io_open_file (struct io_stream *s, const char *file) { struct stat file_stat; s->source = IO_SOURCE_FD; do { s->fd = open (file, O_RDONLY); if (s->fd == -1) { s->errno_val = errno; break; } if (fstat (s->fd, &file_stat) == -1) { s->errno_val = errno; close (s->fd); break; } s->size = file_stat.st_size; s->opened = 1; #ifdef HAVE_MMAP if (!options_get_bool ("UseMMap")) { logit ("Not using mmap()"); s->mem = NULL; break; } s->mem = io_mmap_file (s); if (!s->mem) break; s->source = IO_SOURCE_MMAP; s->mem_pos = 0; #endif } while (0); } /* Open the file. */ struct io_stream *io_open (const char *file, const int buffered) { int rc; struct io_stream *s; assert (file != NULL); s = xmalloc (sizeof(struct io_stream)); s->errno_val = 0; s->read_error = 0; s->strerror = NULL; s->opened = 0; s->size = -1; s->buf_fill_callback = NULL; memset (&s->metadata, 0, sizeof(s->metadata)); #ifdef HAVE_CURL s->curl.mime_type = NULL; if (is_url (file)) io_curl_open (s, file); else #endif io_open_file (s, file); pthread_mutex_init (&s->buf_mtx, NULL); pthread_mutex_init (&s->io_mtx, NULL); pthread_mutex_init (&s->metadata.mtx, NULL); if (!s->opened) return s; s->stop_read_thread = 0; s->eof = 0; s->after_seek = 0; s->buffered = buffered; s->pos = 0; if (buffered) { s->buf = fifo_buf_new (options_get_int("InputBuffer") * 1024); s->prebuffer = options_get_int("Prebuffering") * 1024; pthread_cond_init (&s->buf_free_cond, NULL); pthread_cond_init (&s->buf_fill_cond, NULL); rc = pthread_create (&s->read_thread, NULL, io_read_thread, s); if (rc != 0) fatal ("Can't create read thread: %s", xstrerror (errno)); } return s; } /* Return non-zero if the stream was free of errors. */ static int io_ok_nolock (struct io_stream *s) { return !s->read_error && s->errno_val == 0; } /* Return non-zero if the stream was free of errors. */ int io_ok (struct io_stream *s) { int res; LOCK (s->buf_mtx); res = io_ok_nolock (s); UNLOCK (s->buf_mtx); return res; } /* Read data from the buffer without removing them, so stream position is * unchanged. You can't peek more data than the buffer size. */ static ssize_t io_peek_internal (struct io_stream *s, void *buf, size_t count) { ssize_t received = 0; debug ("Peeking data..."); LOCK (s->buf_mtx); /* Wait until enough data will be available */ while (io_ok_nolock(s) && !s->stop_read_thread && count > fifo_buf_get_fill (s->buf) && fifo_buf_get_space (s->buf) && !s->eof) { debug ("waiting..."); pthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx); } received = fifo_buf_peek (s->buf, buf, count); debug ("Read %zd bytes", received); UNLOCK (s->buf_mtx); return io_ok(s) ? received : -1; } /* Wait until there are s->prebuffer bytes in the buffer or some event * occurs which prevents prebuffering. */ void io_prebuffer (struct io_stream *s, const size_t to_fill) { logit ("prebuffering to %zu bytes...", to_fill); LOCK (s->buf_mtx); while (io_ok_nolock(s) && !s->stop_read_thread && !s->eof && to_fill > fifo_buf_get_fill(s->buf)) { debug ("waiting (buffer %zu bytes full)", fifo_buf_get_fill (s->buf)); pthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx); } UNLOCK (s->buf_mtx); logit ("done"); } static ssize_t io_read_buffered (struct io_stream *s, void *buf, size_t count) { ssize_t received = 0; LOCK (s->buf_mtx); while (received < (ssize_t)count && !s->stop_read_thread && ((!s->eof && !s->read_error) || fifo_buf_get_fill(s->buf))) { if (fifo_buf_get_fill(s->buf)) { received += fifo_buf_get (s->buf, (char *)buf + received, count - received); debug ("Read %zd bytes so far", received); pthread_cond_signal (&s->buf_free_cond); continue; } debug ("Buffer empty, waiting..."); pthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx); } debug ("done"); s->pos += received; UNLOCK (s->buf_mtx); return received ? received : (s->read_error ? -1 : 0); } /* Read data from the stream without buffering. If dont_move was set, the * stream position is unchanged. */ static ssize_t io_read_unbuffered (struct io_stream *s, const int dont_move, void *buf, size_t count) { ssize_t res; assert (!s->eof); res = io_internal_read (s, dont_move, buf, count); if (!dont_move) { s->pos += res; if (res == 0) s->eof = 1; } return res; } /* Read data from the stream to the buffer of size count. Return the number * of bytes read, 0 on EOF, < 0 on error. */ ssize_t io_read (struct io_stream *s, void *buf, size_t count) { ssize_t received; assert (s != NULL); assert (buf != NULL); assert (s->opened); debug ("Reading..."); if (s->buffered) received = io_read_buffered (s, buf, count); else if (s->eof) received = 0; else received = io_read_unbuffered (s, 0, buf, count); return received; } /* Read data from the stream to the buffer of size count. The data are not * removed from the stream. Return the number of bytes read, 0 on EOF, < 0 * on error. */ ssize_t io_peek (struct io_stream *s, void *buf, size_t count) { ssize_t received; assert (s != NULL); assert (buf != NULL); debug ("Reading..."); if (s->buffered) received = io_peek_internal (s, buf, count); else received = io_read_unbuffered (s, 1, buf, count); return io_ok(s) ? received : -1; } /* Get the string describing the error associated with the stream. */ char *io_strerror (struct io_stream *s) { if (s->strerror) free (s->strerror); #ifdef HAVE_CURL if (s->source == IO_SOURCE_CURL) io_curl_strerror (s); else #endif if (s->errno_val) s->strerror = xstrerror (s->errno_val); else s->strerror = xstrdup ("OK"); return s->strerror; } /* Get the file size if available or -1. */ off_t io_file_size (const struct io_stream *s) { assert (s != NULL); return s->size; } /* Return the stream position. */ off_t io_tell (struct io_stream *s) { off_t res = -1; assert (s != NULL); if (s->buffered) { LOCK (s->buf_mtx); res = s->pos; UNLOCK (s->buf_mtx); } else res = s->pos; debug ("We are at byte %"PRId64, res); return res; } /* Return != 0 if we are at the end of the stream. */ int io_eof (struct io_stream *s) { int eof; assert (s != NULL); LOCK (s->buf_mtx); eof = (s->eof && (!s->buffered || !fifo_buf_get_fill(s->buf))) || s->stop_read_thread; UNLOCK (s->buf_mtx); return eof; } void io_init () { #ifdef HAVE_CURL io_curl_init (); #endif } void io_cleanup () { #ifdef HAVE_CURL io_curl_cleanup (); #endif } /* Return the mime type if available or NULL. * The mime type is read by curl only after the first read (or peek), until * then it's NULL. */ char *io_get_mime_type (struct io_stream *s CURL_ONLY) { #ifdef HAVE_CURL return s->curl.mime_type; #else return NULL; #endif } /* Return the malloc()ed stream title if available or NULL. */ char *io_get_metadata_title (struct io_stream *s) { char *t; LOCK (s->metadata.mtx); t = xstrdup (s->metadata.title); UNLOCK (s->metadata.mtx); return t; } /* Return the malloc()ed stream url (from metadata) if available or NULL. */ char *io_get_metadata_url (struct io_stream *s) { char *t; LOCK (s->metadata.mtx); t = xstrdup (s->metadata.url); UNLOCK (s->metadata.mtx); return t; } /* Set the metadata title of the stream. */ void io_set_metadata_title (struct io_stream *s, const char *title) { LOCK (s->metadata.mtx); if (s->metadata.title) free (s->metadata.title); s->metadata.title = xstrdup (title); UNLOCK (s->metadata.mtx); } /* Set the metadata url for the stream. */ void io_set_metadata_url (struct io_stream *s, const char *url) { LOCK (s->metadata.mtx); if (s->metadata.url) free (s->metadata.url); s->metadata.url = xstrdup (url); UNLOCK (s->metadata.mtx); } /* Set the callback function to be invoked when the fill of the buffer * changes. data_ptr is a pointer passed to this function along with * the pointer to the stream. */ void io_set_buf_fill_callback (struct io_stream *s, buf_fill_callback_t callback, void *data_ptr) { assert (s != NULL); assert (callback != NULL); LOCK (s->buf_mtx); s->buf_fill_callback = callback; s->buf_fill_callback_data = data_ptr; UNLOCK (s->buf_mtx); } /* Return a non-zero value if the stream is seekable. */ int io_seekable (const struct io_stream *s) { return s->source == IO_SOURCE_FD || s->source == IO_SOURCE_MMAP; } moc-2.6.0~svn-r3005/io.h0000664000076400000500000000751312673636265013735 0ustar riesebiesrc#ifndef IO_H #define IO_H #include #include #ifdef HAVE_CURL # include /* curl sometimes needs this */ # include #endif #include "fifo_buf.h" #ifdef __cplusplus extern "C" { #endif enum io_source { IO_SOURCE_FD, IO_SOURCE_MMAP, IO_SOURCE_CURL }; #ifdef HAVE_CURL struct io_stream_curl { CURLM *multi_handle; /* we use the multi interface to get the data in pieces */ CURL *handle; /* the actual used handle */ CURLMcode multi_status; /* curl status of the last multi operation */ CURLcode status; /* curl status of the last easy operation */ char *url; struct curl_slist *http_headers; /* HTTP headers to send with the request */ char *buf; /* buffer for data that curl gives us */ long buf_fill; int need_perform_loop; /* do we need the perform() loop? */ int got_locn; /* received a location header */ char *mime_type; /* mime type of the stream */ int wake_up_pipe[2]; /* pipes used to wake up the curl read loop that does select() */ struct curl_slist *http200_aliases; /* list of aliases for http response's status line */ size_t icy_meta_int; /* how often are icy metadata sent? 0 - disabled, in bytes */ size_t icy_meta_count; /* how many bytes was read from the last metadata packet */ }; #endif struct io_stream; typedef void (*buf_fill_callback_t) (struct io_stream *s, size_t fill, size_t buf_size, void *data_ptr); struct io_stream { enum io_source source; /* source of the file */ int fd; off_t size; /* size of the file */ int errno_val; /* errno value of the last operation - 0 if ok */ int read_error; /* set to != 0 if the last read operation dailed */ char *strerror; /* error string */ int opened; /* was the stream opened (open(), mmap(), etc.)? */ int eof; /* was the end of file reached? */ int after_seek; /* are we after seek and need to do fresh read()? */ int buffered; /* are we using the buffer? */ off_t pos; /* current position in the file from the user point of view */ size_t prebuffer; /* number of bytes left to prebuffer */ pthread_mutex_t io_mtx; /* mutex for IO operations */ #ifdef HAVE_MMAP void *mem; off_t mem_pos; #endif #ifdef HAVE_CURL struct io_stream_curl curl; #endif struct fifo_buf *buf; pthread_mutex_t buf_mtx; pthread_cond_t buf_free_cond; /* some space became available in the buffer */ pthread_cond_t buf_fill_cond; /* the buffer was filled with some data */ pthread_t read_thread; int stop_read_thread; /* request for stopping the read thread */ struct stream_metadata { pthread_mutex_t mtx; char *title; /* title of the stream */ char *url; } metadata; /* callbacks */ buf_fill_callback_t buf_fill_callback; void *buf_fill_callback_data; }; struct io_stream *io_open (const char *file, const int buffered); ssize_t io_read (struct io_stream *s, void *buf, size_t count); ssize_t io_peek (struct io_stream *s, void *buf, size_t count); off_t io_seek (struct io_stream *s, off_t offset, int whence); void io_close (struct io_stream *s); int io_ok (struct io_stream *s); char *io_strerror (struct io_stream *s); off_t io_file_size (const struct io_stream *s); off_t io_tell (struct io_stream *s); int io_eof (struct io_stream *s); void io_init (); void io_cleanup (); void io_abort (struct io_stream *s); char *io_get_mime_type (struct io_stream *s); char *io_get_title (struct io_stream *s); char *io_get_metadata_title (struct io_stream *s); char *io_get_metadata_url (struct io_stream *s); void io_set_metadata_title (struct io_stream *s, const char *title); void io_set_metadata_url (struct io_stream *s, const char *url); void io_prebuffer (struct io_stream *s, const size_t to_fill); void io_set_buf_fill_callback (struct io_stream *s, buf_fill_callback_t callback, void *data_ptr); int io_seekable (const struct io_stream *s); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/io_curl.c0000664000076400000500000003517212713510014014731 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "log.h" #include "io.h" #include "io_curl.h" #include "options.h" #include "lists.h" static char user_agent[] = PACKAGE_NAME"/"PACKAGE_VERSION; void io_curl_init () { char *ptr; for (ptr = user_agent; *ptr; ptr += 1) { if (*ptr == ' ') *ptr = '-'; } curl_global_init (CURL_GLOBAL_NOTHING); } void io_curl_cleanup () { curl_global_cleanup (); } static size_t write_cb (void *data, size_t size, size_t nmemb, void *stream) { struct io_stream *s = (struct io_stream *)stream; size_t buf_start = s->curl.buf_fill; size_t data_size = size * nmemb; s->curl.buf_fill += data_size; debug ("Got %zu bytes", data_size); s->curl.buf = (char *)xrealloc (s->curl.buf, s->curl.buf_fill); memcpy (s->curl.buf + buf_start, data, data_size); return data_size; } static size_t header_cb (void *data, size_t size, size_t nmemb, void *stream) { struct io_stream *s = (struct io_stream *)stream; char *header; size_t header_size; assert (s != NULL); if (size * nmemb <= 2) return size * nmemb; /* we dont need '\r\n', so cut it. */ header_size = sizeof(char) * (size * nmemb + 1 - 2); /* copy the header to char* array */ header = (char *)xmalloc (header_size); memcpy (header, data, size * nmemb - 2); header[header_size-1] = 0; if (!strncasecmp(header, "Location:", sizeof("Location:")-1)) { s->curl.got_locn = 1; } else if (!strncasecmp(header, "Content-Type:", sizeof("Content-Type:")-1)) { /* If we got redirected then use the last MIME type. */ if (s->curl.got_locn && s->curl.mime_type) { free (s->curl.mime_type); s->curl.mime_type = NULL; } if (s->curl.mime_type) logit ("Another Content-Type header!"); else { char *value = header + sizeof("Content-Type:") - 1; while (isblank(value[0])) value++; s->curl.mime_type = xstrdup (value); debug ("Mime type: '%s'", s->curl.mime_type); } } else if (!strncasecmp(header, "icy-name:", sizeof("icy-name:")-1) || !strncasecmp(header, "x-audiocast-name", sizeof("x-audiocast-name")-1)) { char *value = strchr (header, ':') + 1; while (isblank(value[0])) value++; io_set_metadata_title (s, value); } else if (!strncasecmp(header, "icy-url:", sizeof("icy-url:")-1)) { char *value = strchr (header, ':') + 1; while (isblank(value[0])) value++; io_set_metadata_url (s, value); } else if (!strncasecmp(header, "icy-metaint:", sizeof("icy-metaint:")-1)) { char *end; char *value = strchr (header, ':') + 1; while (isblank(value[0])) value++; s->curl.icy_meta_int = strtol (value, &end, 10); if (*end) { s->curl.icy_meta_int = 0; logit ("Bad icy-metaint value"); } else debug ("Icy metadata interval: %zu", s->curl.icy_meta_int); } free (header); return size * nmemb; } #if !defined(NDEBUG) && defined(DEBUG) static int debug_cb (CURL *unused1 ATTR_UNUSED, curl_infotype i, char *msg, size_t size, void *unused2 ATTR_UNUSED) { int ix; char *log; const char *type; lists_t_strs *lines; switch (i) { case CURLINFO_TEXT: type = "INFO"; break; case CURLINFO_HEADER_IN: type = "RECV HEADER"; break; case CURLINFO_HEADER_OUT: type = "SEND HEADER"; break; default: return 0; } log = (char *)xmalloc (size + 1); strncpy (log, msg, size); log[size] = 0; lines = lists_strs_new (8); lists_strs_split (lines, log, "\n"); for (ix = 0; ix < lists_strs_size (lines); ix += 1) debug ("CURL: [%s] %s", type, lists_strs_at (lines, ix)); lists_strs_free (lines); free (log); return 0; } #endif /* Read messages given by curl and set the stream status. Return 0 on error. */ static int check_curl_stream (struct io_stream *s) { CURLMsg *msg; int msg_queue_num; int res = 1; while ((msg = curl_multi_info_read (s->curl.multi_handle, &msg_queue_num))) { if (msg->msg == CURLMSG_DONE) { s->curl.status = msg->data.result; if (s->curl.status != CURLE_OK) { debug ("Read error"); res = 0; } curl_multi_remove_handle (s->curl.multi_handle, s->curl.handle); curl_easy_cleanup (s->curl.handle); s->curl.handle = NULL; debug ("EOF"); break; } } return res; } void io_curl_open (struct io_stream *s, const char *url) { s->source = IO_SOURCE_CURL; s->curl.url = NULL; s->curl.http_headers = NULL; s->curl.buf = NULL; s->curl.buf_fill = 0; s->curl.need_perform_loop = 1; s->curl.got_locn = 0; s->curl.wake_up_pipe[0] = -1; s->curl.wake_up_pipe[1] = -1; if (!(s->curl.multi_handle = curl_multi_init())) { logit ("curl_multi_init() returned NULL"); s->errno_val = EINVAL; return; } if (!(s->curl.handle = curl_easy_init())) { logit ("curl_easy_init() returned NULL"); s->errno_val = EINVAL; return; } s->curl.multi_status = CURLM_OK; s->curl.status = CURLE_OK; s->curl.url = xstrdup (url); s->curl.icy_meta_int = 0; s->curl.icy_meta_count = 0; s->curl.http200_aliases = curl_slist_append (NULL, "ICY"); s->curl.http_headers = curl_slist_append (NULL, "Icy-MetaData: 1"); curl_easy_setopt (s->curl.handle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt (s->curl.handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_easy_setopt (s->curl.handle, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt (s->curl.handle, CURLOPT_WRITEDATA, s); curl_easy_setopt (s->curl.handle, CURLOPT_HEADERFUNCTION, header_cb); curl_easy_setopt (s->curl.handle, CURLOPT_WRITEHEADER, s); curl_easy_setopt (s->curl.handle, CURLOPT_USERAGENT, user_agent); curl_easy_setopt (s->curl.handle, CURLOPT_URL, s->curl.url); curl_easy_setopt (s->curl.handle, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt (s->curl.handle, CURLOPT_FAILONERROR, 1); curl_easy_setopt (s->curl.handle, CURLOPT_MAXREDIRS, 15); curl_easy_setopt (s->curl.handle, CURLOPT_HTTP200ALIASES, s->curl.http200_aliases); curl_easy_setopt (s->curl.handle, CURLOPT_HTTPHEADER, s->curl.http_headers); if (options_get_str("HTTPProxy")) curl_easy_setopt (s->curl.handle, CURLOPT_PROXY, options_get_str("HTTPProxy")); #if !defined(NDEBUG) && defined(DEBUG) curl_easy_setopt (s->curl.handle, CURLOPT_VERBOSE, 1); curl_easy_setopt (s->curl.handle, CURLOPT_DEBUGFUNCTION, debug_cb); #endif if ((s->curl.multi_status = curl_multi_add_handle(s->curl.multi_handle, s->curl.handle)) != CURLM_OK) { logit ("curl_multi_add_handle() failed"); s->errno_val = EINVAL; return; } if (pipe(s->curl.wake_up_pipe) < 0) { log_errno ("pipe() failed", errno); s->errno_val = EINVAL; return; } s->opened = 1; } void io_curl_close (struct io_stream *s) { assert (s != NULL); assert (s->source == IO_SOURCE_CURL); if (s->curl.url) free (s->curl.url); if (s->curl.http_headers) curl_slist_free_all (s->curl.http_headers); if (s->curl.buf) free (s->curl.buf); if (s->curl.mime_type) free (s->curl.mime_type); if (s->curl.multi_handle && s->curl.handle) curl_multi_remove_handle (s->curl.multi_handle, s->curl.handle); if (s->curl.handle) curl_easy_cleanup (s->curl.handle); if (s->curl.multi_handle) curl_multi_cleanup (s->curl.multi_handle); if (s->curl.wake_up_pipe[0] != -1) { close (s->curl.wake_up_pipe[0]); close (s->curl.wake_up_pipe[1]); } if (s->curl.http200_aliases) curl_slist_free_all (s->curl.http200_aliases); } /* Get data using curl and put them into the internal buffer. * Even if the internal buffer is not empty, more data will be read. * Return 0 on error. */ static int curl_read_internal (struct io_stream *s) { int running = 1; long buf_fill_before = s->curl.buf_fill; if (s->curl.need_perform_loop) { debug ("Starting curl..."); do { s->curl.multi_status = curl_multi_perform ( s->curl.multi_handle, &running); } while (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM); if (!check_curl_stream(s)) return 0; s->curl.need_perform_loop = 0; } while (s->opened && running && buf_fill_before == s->curl.buf_fill && s->curl.handle && (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM || s->curl.multi_status == CURLM_OK)) { if (s->curl.multi_status != CURLM_CALL_MULTI_PERFORM) { fd_set read_fds, write_fds, exc_fds; int max_fd, ret; long milliseconds; struct timespec timeout; logit ("Doing pselect()..."); FD_ZERO (&read_fds); FD_ZERO (&write_fds); FD_ZERO (&exc_fds); s->curl.multi_status = curl_multi_fdset ( s->curl.multi_handle, &read_fds, &write_fds, &exc_fds, &max_fd); if (s->curl.multi_status != CURLM_OK) logit ("curl_multi_fdset() failed"); FD_SET (s->curl.wake_up_pipe[0], &read_fds); if (s->curl.wake_up_pipe[0] > max_fd) max_fd = s->curl.wake_up_pipe[0]; curl_multi_timeout (s->curl.multi_handle, &milliseconds); if (milliseconds <= 0) milliseconds = 1000; timeout.tv_sec = milliseconds / 1000; timeout.tv_nsec = (milliseconds % 1000L) * 1000000L; ret = pselect (max_fd + 1, &read_fds, &write_fds, &exc_fds, &timeout, NULL); if (ret < 0 && errno == EINTR) { logit ("Interrupted"); return 0; } if (ret < 0) { s->errno_val = errno; logit ("pselect() failed"); return 0; } if (s->stop_read_thread) return 1; if (FD_ISSET(s->curl.wake_up_pipe[0], &read_fds)) { logit ("Got wake up - exiting"); return 1; } } s->curl.multi_status = curl_multi_perform (s->curl.multi_handle, &running); if (!check_curl_stream(s)) return 0; } return 1; } /* Read data from the internal buffer to buf. Return the number of bytes read. */ static size_t read_from_buffer (struct io_stream *s, char *buf, size_t count) { if (s->curl.buf_fill) { long to_copy = MIN ((long)count, s->curl.buf_fill); /*debug ("Copying %ld bytes", to_copy);*/ memcpy (buf, s->curl.buf, to_copy); s->curl.buf_fill -= to_copy; if (s->curl.buf_fill) { memmove (s->curl.buf, s->curl.buf + to_copy, s->curl.buf_fill); s->curl.buf = (char *)xrealloc (s->curl.buf, s->curl.buf_fill); } else { free (s->curl.buf); s->curl.buf = NULL; } return to_copy; } return 0; } /* Parse icy string in form: StreamTitle='my music';StreamUrl='www.x.com' */ static void parse_icy_string (struct io_stream *s, const char *str) { const char *c = str; debug ("Got metadata string: %s", str); while (*c) { char name[64]; char value[256]; const char *t; /* get the name */ t = c; while (*c && *c != '=') c++; if (*c != '=' || c - t >= ssizeof(name)) { logit ("malformed metadata"); return; } strncpy (name, t, c - t); name[c - t] = 0; /* move to a char after ' */ c++; if (*c != '\'') { logit ("malformed metadata"); return; } c++; /* read the value - it can contain a quotation mark so we * recognize if it ends the value by checking if there is a * semicolon after it or if it's the end of the string */ t = c; while (*c && (*c != '\'' || (*(c+1) != ';' && *(c+1)))) c++; if (!*c) { logit ("malformed metadata"); return; } strncpy (value, t, MIN(c - t, ssizeof(value) - 1)); value[MIN(c - t, ssizeof(value) - 1)] = 0; /* eat ' */ c++; /* eat semicolon */ if (*c == ';') c++; debug ("METADATA name: '%s' value: '%s'", name, value); if (!strcasecmp(name, "StreamTitle")) io_set_metadata_title (s, value); else if (!strcasecmp(name, "StreamUrl")) io_set_metadata_url (s, value); else logit ("Unknown metadata element '%s'", name); } } /* Parse the IceCast metadata packet. */ static void parse_icy_metadata (struct io_stream *s, const char *packet, const int size) { const char *c = packet; while (c - packet < size) { const char *p = c; while (*c && c - packet < size) c++; if (c - packet < size && !*c) parse_icy_string (s, p); /* pass the padding */ while (c - packet < size && !*c) c++; } } /* Read icy metadata from the curl stream. The stream should be at the * beginning of the metadata. Return 0 on error. */ static int read_icy_metadata (struct io_stream *s) { uint8_t size_packet; int size; char *packet; /* read the packet size */ if (s->curl.buf_fill == 0 && !curl_read_internal(s)) return 0; if (read_from_buffer(s, (char *)&size_packet, sizeof(size_packet)) == 0) { debug ("Got empty metadata packet"); return 1; } if (size_packet == 0) { debug ("Got empty metadata packet"); return 1; } size = size_packet * 16; /* make sure that the whole packet is in the buffer */ while (s->curl.buf_fill < size && s->curl.handle && !s->stop_read_thread) if (!curl_read_internal(s)) return 0; if (s->curl.buf_fill < size) { logit ("Icy metadata packet broken"); return 0; } packet = (char *)xmalloc (size); read_from_buffer (s, packet, size); debug ("Received metadata packet %d bytes long", size); parse_icy_metadata (s, packet, size); free (packet); return 1; } ssize_t io_curl_read (struct io_stream *s, char *buf, size_t count) { size_t nread = 0; assert (s != NULL); assert (s->source == IO_SOURCE_CURL); assert (s->curl.multi_handle != NULL); do { size_t to_read; size_t res; if (s->curl.icy_meta_int && s->curl.icy_meta_count == s->curl.icy_meta_int) { s->curl.icy_meta_count = 0; if (!read_icy_metadata(s)) return -1; } if (s->curl.icy_meta_int) to_read = MIN (count - nread, s->curl.icy_meta_int - s->curl.icy_meta_count); else to_read = count - nread; res = read_from_buffer (s, buf + nread, to_read); if (s->curl.icy_meta_int) s->curl.icy_meta_count += res; nread += res; debug ("Read %zu bytes from the buffer (%zu bytes full)", res, nread); if (nread < count && !curl_read_internal(s)) return -1; } while (nread < count && !s->stop_read_thread && s->curl.handle); /* s->curl.handle == NULL on EOF */ return nread; } /* Set the error string for the stream. */ void io_curl_strerror (struct io_stream *s) { const char *err = "OK"; assert (s != NULL); assert (s->source == IO_SOURCE_CURL); if (s->curl.multi_status != CURLM_OK) err = curl_multi_strerror(s->curl.multi_status); else if (s->curl.status != CURLE_OK) err = curl_easy_strerror(s->curl.status); s->strerror = xstrdup (err); } void io_curl_wake_up (struct io_stream *s) { int w = 1; if (write(s->curl.wake_up_pipe[1], &w, sizeof(w)) < 0) log_errno ("Can't wake up curl thread: write() failed", errno); } moc-2.6.0~svn-r3005/io_curl.h0000664000076400000500000000066411322047704014742 0ustar riesebiesrc#ifndef IO_CURL_H #define IO_CURL_H #include "io.h" #ifdef __cplusplus extern "C" { #endif void io_curl_init (); void io_curl_cleanup (); void io_curl_open (struct io_stream *s, const char *url); void io_curl_close (struct io_stream *s); ssize_t io_curl_read (struct io_stream *s, char *buf, size_t count); void io_curl_strerror (struct io_stream *s); void io_curl_wake_up (struct io_stream *s); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/jack.c0000664000076400000500000002161712424322553014214 0ustar riesebiesrc/* Jack plugin for moc by Alex Norman 2005 * moc by Copyright (C) 2004 Damian Pietras * use at your own risk */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "audio.h" #include "log.h" #include "options.h" #define RINGBUF_SZ 32768 /* the client */ static jack_client_t *client; /* an array of output ports */ static jack_port_t **output_port; /* the ring buffer, used to store the sound data before jack takes it */ static jack_ringbuffer_t *ringbuffer[2]; /* volume */ static jack_default_audio_sample_t volume = 1.0; /* volume as an integer - needed to avoid cast errors on set/read */ static int volume_integer = 100; /* indicates if we should be playing or not */ static int play; /* current sample rate */ static int rate; /* flag set if xrun occurred that was our fault (the ringbuffer doesn't * contain enough data in the process callback) */ static volatile int our_xrun = 0; /* set to 1 if jack client thread exits */ static volatile int jack_shutdown = 0; /* this is the function that jack calls to get audio samples from us */ static int process_cb(jack_nframes_t nframes, void *unused ATTR_UNUSED) { jack_default_audio_sample_t *out[2]; if (nframes <= 0) return 0; /* get the jack output ports */ out[0] = (jack_default_audio_sample_t *) jack_port_get_buffer ( output_port[0], nframes); out[1] = (jack_default_audio_sample_t *) jack_port_get_buffer ( output_port[1], nframes); if (play) { size_t i; /* ringbuffer[1] is filled later, so we only need to check * it's space. */ size_t avail_data = jack_ringbuffer_read_space(ringbuffer[1]); size_t avail_frames = avail_data / sizeof(jack_default_audio_sample_t); if (avail_frames > nframes) { avail_frames = nframes; avail_data = nframes * sizeof(jack_default_audio_sample_t); } jack_ringbuffer_read (ringbuffer[0], (char *)out[0], avail_data); jack_ringbuffer_read (ringbuffer[1], (char *)out[1], avail_data); /* we must provide nframes data, so fill with silence * the remaining space. */ if (avail_frames < nframes) { our_xrun = 1; for (i = avail_frames; i < nframes; i++) out[0][i] = out[1][i] = 0.0; } } else { size_t i; size_t size; /* consume the input */ size = jack_ringbuffer_read_space(ringbuffer[1]); jack_ringbuffer_read_advance (ringbuffer[0], size); jack_ringbuffer_read_advance (ringbuffer[1], size); for (i = 0; i < nframes; i++) { out[0][i] = 0.0; out[1][i] = 0.0; } } return 0; } /* this is called if jack changes its sample rate */ static int update_sample_rate_cb(jack_nframes_t new_rate, void *unused ATTR_UNUSED) { rate = new_rate; return 0; } /* callback for jack's error messages */ static void error_cb (const char *msg) { error ("JACK: %s", msg); } static void shutdown_cb (void *unused ATTR_UNUSED) { jack_shutdown = 1; } static int moc_jack_init (struct output_driver_caps *caps) { const char *client_name; client_name = options_get_str ("JackClientName"); jack_set_error_function (error_cb); #ifdef HAVE_JACK_CLIENT_OPEN jack_status_t status; jack_options_t options; /* open a client connection to the JACK server */ options = JackNullOption; if (!options_get_bool ("JackStartServer")) options |= JackNoStartServer; client = jack_client_open (client_name, options, &status, NULL); if (client == NULL) { error ("jack_client_open() failed, status = 0x%2.0x", status); if (status & JackServerFailed) error ("Unable to connect to JACK server"); return 0; } if (status & JackServerStarted) printf ("JACK server started\n"); #else /* try to become a client of the JACK server */ client = jack_client_new (client_name); if (client == NULL) { error ("Cannot create client; JACK server not running?"); return 0; } #endif jack_shutdown = 0; jack_on_shutdown (client, shutdown_cb, NULL); /* allocate memory for an array of 2 output ports */ output_port = xmalloc(2 * sizeof(jack_port_t *)); output_port[0] = jack_port_register (client, "output0", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); output_port[1] = jack_port_register (client, "output1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); /* create the ring buffers */ ringbuffer[0] = jack_ringbuffer_create(RINGBUF_SZ); ringbuffer[1] = jack_ringbuffer_create(RINGBUF_SZ); /* set the call back functions, activate the client */ jack_set_process_callback (client, process_cb, NULL); jack_set_sample_rate_callback(client, update_sample_rate_cb, NULL); if (jack_activate (client)) { error ("cannot activate client"); return 0; } /* connect ports * a value of NULL in JackOut* gives no connection * */ if(strcmp(options_get_str("JackOutLeft"),"NULL")){ if(jack_connect(client,"moc:output0", options_get_str("JackOutLeft"))) fprintf(stderr,"%s is not a valid Jack Client / Port", options_get_str("JackOutLeft")); } if(strcmp(options_get_str("JackOutRight"),"NULL")){ if(jack_connect(client,"moc:output1", options_get_str("JackOutRight"))) fprintf(stderr,"%s is not a valid Jack Client / Port", options_get_str("JackOutRight")); } caps->formats = SFMT_FLOAT; rate = jack_get_sample_rate (client); caps->max_channels = caps->min_channels = 2; logit ("jack init"); return 1; } static int moc_jack_open (struct sound_params *sound_params) { if (sound_params->fmt != SFMT_FLOAT) { char fmt_name[SFMT_STR_MAX]; error ("Unsupported sound format: %s.", sfmt_str(sound_params->fmt, fmt_name, sizeof(fmt_name))); return 0; } if (sound_params->channels != 2) { error ("Unsupported number of channels"); return 0; } logit ("jack open"); play = 1; return 1; } static void moc_jack_close () { logit ("jack close"); play = 0; } static int moc_jack_play (const char *buff, const size_t size) { size_t remain = size; size_t pos = 0; if (jack_shutdown) { logit ("Refusing to play, because there is no client thread."); return -1; } debug ("Playing %zu bytes", size); if (our_xrun) { logit ("xrun"); our_xrun = 0; } while (remain && !jack_shutdown) { size_t space; /* check if some space is available only in the second * ringbuffer, because it is read later than the first. */ if ((space = jack_ringbuffer_write_space(ringbuffer[1])) > sizeof(jack_default_audio_sample_t)) { size_t to_write; space *= 2; /* we have 2 channels */ debug ("Space in the ringbuffer: %zu bytes", space); to_write = MIN (space, remain); to_write /= sizeof(jack_default_audio_sample_t) * 2; remain -= to_write * sizeof(float) * 2; while (to_write--) { jack_default_audio_sample_t sample; sample = *(jack_default_audio_sample_t *) (buff + pos) * volume; pos += sizeof (jack_default_audio_sample_t); jack_ringbuffer_write (ringbuffer[0], (char *)&sample, sizeof(sample)); sample = *(jack_default_audio_sample_t *) (buff + pos) * volume; pos += sizeof (jack_default_audio_sample_t); jack_ringbuffer_write (ringbuffer[1], (char *)&sample, sizeof(sample)); } } else { debug ("Sleeping for %uus", (unsigned int)(RINGBUF_SZ / (float)(audio_get_bps()) * 100000.0)); xsleep (RINGBUF_SZ, audio_get_bps ()); } } if (jack_shutdown) return -1; return size; } static int moc_jack_read_mixer () { return volume_integer; } static void moc_jack_set_mixer (int vol) { volume_integer = vol; volume = (jack_default_audio_sample_t)((exp((double)vol / 100.0) - 1) / (M_E - 1)); } static int moc_jack_get_buff_fill () { /* FIXME: should we also use jack_port_get_latency() here? */ return sizeof(float) * (jack_ringbuffer_read_space(ringbuffer[0]) + jack_ringbuffer_read_space(ringbuffer[1])) / sizeof(jack_default_audio_sample_t); } static int moc_jack_reset () { //jack_ringbuffer_reset(ringbuffer); /*this is not threadsafe!*/ return 1; } /* do any cleanup that needs to be done */ static void moc_jack_shutdown(){ jack_port_unregister(client,output_port[0]); jack_port_unregister(client,output_port[1]); free(output_port); jack_client_close(client); jack_ringbuffer_free(ringbuffer[0]); jack_ringbuffer_free(ringbuffer[1]); } static int moc_jack_get_rate () { return rate; } static char *moc_jack_get_mixer_channel_name () { return xstrdup ("soft mixer"); } static void moc_jack_toggle_mixer_channel () { } void moc_jack_funcs (struct hw_funcs *funcs) { funcs->init = moc_jack_init; funcs->open = moc_jack_open; funcs->close = moc_jack_close; funcs->play = moc_jack_play; funcs->read_mixer = moc_jack_read_mixer; funcs->set_mixer = moc_jack_set_mixer; funcs->get_buff_fill = moc_jack_get_buff_fill; funcs->reset = moc_jack_reset; funcs->shutdown = moc_jack_shutdown; funcs->get_rate = moc_jack_get_rate; funcs->get_mixer_channel_name = moc_jack_get_mixer_channel_name; funcs->toggle_mixer_channel = moc_jack_toggle_mixer_channel; } moc-2.6.0~svn-r3005/jack.h0000664000076400000500000000023211322047704014205 0ustar riesebiesrc#ifndef JACK_H #define JACK_H #ifdef __cplusplus extern "C" { #endif void moc_jack_funcs (struct hw_funcs *funcs); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/keymap.example0000664000076400000500000001310212713507747016004 0ustar riesebiesrc# This is the example keymap file for MOC. You can define your own key # bindings for MOC commands by creating your own keymap file and setting # the 'Keymap' option in ~/.moc/config. # # The format of this file is: # # - Lines beginning with # are comments. # - Blank lines are ignored. # - Every other line is expected to be in one of the formats: # # COMMAND = [KEY ...] # COMMAND += KEY ... # # The KEY can be: # # - Just a char, like i, L, ", * # - CTRL-KEY sequence: ^k (CTRL-k), ^4 # - ALT-KEY (meta) sequence: M-j (ALT-j), M-/ # - Special keys: DOWN, UP # LEFT, RIGHT # HOME, END # BACKSPACE # INS, DEL # ENTER # PAGE_UP, PAGE_DOWN # SPACE, TAB # KEYPAD_CENTER # ESCAPE # F1 - F12 # # Note that the use of a digit as a KEY is deprecated. # # Maximum number of KEYs for one COMMAND is 5. # # Omitting the KEY for a COMMAND will unbind all its default keys. They # will also be automatically unbound when you bind new KEYs to it. Individual # default KEYs will be automatically unbound when they are explicitly bound # to some other COMMAND. # # Using the '+=' form will cause the KEYs to be appended to any existing # (default or explicit) bindings for the COMMAND. Appending an existing # default binding for the same COMMAND will cause MOC to think of that KEY # as then being explicitly bound. # # Only one binding for any given COMMAND can appear in the keymap file. One # exception to this is that if the default keys for a COMMAND are explicitly # unbound then a subsequent binding may appear for it. A second exception # is that multiple appending bindings may appear. # # Meta-key detection is sensitive to the ESCDELAY environment variable (see # the manpage for ncurses(3)). In its absence, MOC resets the default # delay to 25ms. If you need to emulate meta-key sequences using the ESC # key, then you may need to set the value of ESCDELAY back to its ncurses # default of 1000ms (but doing so will make the response to the ESC key # sluggish). # # If MOC's keypresses are being filtered through some other program (in a # GUI environment, for example) which also does meta-key detection, then # MOC is at the mercy of the timings with which that program presents them. # # Default key configuration for MOC (and a list of all available commands): # MOC control keys: quit_client = q quit = Q # Menu and interface control keys: go = ENTER menu_down = DOWN menu_up = UP menu_page_down = PAGE_DOWN menu_page_up = PAGE_UP menu_first_item = HOME menu_last_item = END search_menu = g / toggle_read_tags = f toggle_show_time = ^t toggle_show_format = ^f toggle_menu = TAB toggle_layout = l toggle_hidden_files = H show_lyrics = L theme_menu = T help = h ? refresh = ^r reload = r # Audio playing and positioning keys: seek_forward = RIGHT seek_backward = LEFT seek_forward_fast = ] seek_backward_fast = [ pause = p SPACE stop = s next = n previous = b toggle_shuffle = S toggle_repeat = R toggle_auto_next = X toggle_mixer = x go_url = o # Volume control keys: volume_down_1 = < volume_up_1 = > volume_down_5 = , volume_up_5 = . volume_10 = M-1 volume_20 = M-2 volume_30 = M-3 volume_40 = M-4 volume_50 = M-5 volume_60 = M-6 volume_70 = M-7 volume_80 = M-8 volume_90 = M-9 # Directory navigation keys: defaults are Shift-number # (i.e., 'shift 1' -> '!' -> 'Fastdir1'). go_to_a_directory = i go_to_music_directory = m go_to_fast_dir1 = ! go_to_fast_dir2 = @ go_to_fast_dir3 = # go_to_fast_dir4 = $ go_to_fast_dir5 = % go_to_fast_dir6 = ^ go_to_fast_dir7 = & go_to_fast_dir8 = * go_to_fast_dir9 = ( go_to_fast_dir10 = ) go_to_playing_file = G go_up = U # Playlist specific keys: add_file = a add_directory = A plist_add_stream = ^u delete_from_playlist = d playlist_full_paths = P plist_move_up = u plist_move_down = j save_playlist = V remove_dead_entries = Y clear_playlist = C # Queue manipulation keys: enqueue_file = z clear_queue = Z # User interaction control: history_up = UP history_down = DOWN delete_to_start = ^u delete_to_end = ^k cancel = ^x ESCAPE hide_message = M # Softmixer specific keys: toggle_softmixer = w toggle_make_mono = J # Equalizer specific keys: toggle_equalizer = E equalizer_refresh = e equalizer_prev = K equalizer_next = k # External commands: mark_start = ' mark_end = " exec_command1 = F1 exec_command2 = F2 exec_command3 = F3 exec_command4 = F4 exec_command5 = F5 exec_command6 = F6 exec_command7 = F7 exec_command8 = F8 exec_command9 = F9 exec_command10 = F10 # The following commands are available but not assigned to any keys by # default: # # toggle_percent Switch on/off play progress bar time percentage # moc-2.6.0~svn-r3005/keys.c0000664000076400000500000005265512713507747014300 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #define DEBUG #include "common.h" #include "keys.h" #include "interface.h" #include "interface_elements.h" #include "options.h" #include "log.h" #include "files.h" /* ^c version of c */ #ifndef CTRL # define CTRL(c) ((c) & CTRL_KEY_CODE) #endif struct command { enum key_cmd cmd; /* the command */ char *name; /* name of the command (in keymap file) */ char *help; /* help string for the command */ enum key_context context; /* context - where the command isused */ int keys[6]; /* array of keys ended with -1 */ int default_keys; /* number of default keys */ }; /* Array of commands - each element is a list of keys for this command. */ static struct command commands[] = { { KEY_CMD_QUIT_CLIENT, "quit_client", "Detach MOC from the server", CON_MENU, { 'q', -1 }, 1 }, { KEY_CMD_GO, "go", "Start playing at this file or go to this directory", CON_MENU, { '\n', -1 }, 1 }, { KEY_CMD_MENU_DOWN, "menu_down", "Move down in the menu", CON_MENU, { KEY_DOWN, -1 }, 1 }, { KEY_CMD_MENU_UP, "menu_up", "Move up in the menu", CON_MENU, { KEY_UP, -1 }, 1 }, { KEY_CMD_MENU_NPAGE, "menu_page_down", "Move one page down", CON_MENU, { KEY_NPAGE, -1}, 1 }, { KEY_CMD_MENU_PPAGE, "menu_page_up", "Move one page up", CON_MENU, { KEY_PPAGE, -1}, 1 }, { KEY_CMD_MENU_FIRST, "menu_first_item", "Move to the first item in the menu", CON_MENU, { KEY_HOME, -1 }, 1 }, { KEY_CMD_MENU_LAST, "menu_last_item", "Move to the last item in the menu", CON_MENU, { KEY_END, -1 }, 1 }, { KEY_CMD_QUIT, "quit", "Quit", CON_MENU, { 'Q', -1 }, 1 }, { KEY_CMD_STOP, "stop", "Stop", CON_MENU, { 's', -1 }, 1 }, { KEY_CMD_NEXT, "next", "Play next file", CON_MENU, { 'n', -1 }, 1 }, { KEY_CMD_PREVIOUS, "previous", "Play previous file", CON_MENU, { 'b', -1 }, 1 }, { KEY_CMD_PAUSE, "pause", "Pause", CON_MENU, { 'p', ' ', -1 }, 2 }, { KEY_CMD_TOGGLE_READ_TAGS, "toggle_read_tags", "Toggle ReadTags option", CON_MENU, { 'f', -1 }, 1 }, { KEY_CMD_TOGGLE_SHUFFLE, "toggle_shuffle", "Toggle Shuffle", CON_MENU, { 'S', -1 }, 1 }, { KEY_CMD_TOGGLE_REPEAT, "toggle_repeat", "Toggle Repeat", CON_MENU, { 'R', -1 }, 1 }, { KEY_CMD_TOGGLE_AUTO_NEXT, "toggle_auto_next", "Toggle AutoNext", CON_MENU, { 'X', -1 }, 1 }, { KEY_CMD_TOGGLE_MENU, "toggle_menu", "Switch between playlist and file list", CON_MENU, { '\t', -1 }, 1 }, { KEY_CMD_TOGGLE_LAYOUT, "toggle_layout", "Switch between layouts", CON_MENU, { 'l', -1 }, 1 }, { KEY_CMD_TOGGLE_PERCENT, "toggle_percent", "Switch on/off play time percentage", CON_MENU, { -1 }, 0 }, { KEY_CMD_PLIST_ADD_FILE, "add_file", "Add a file/directory to the playlist", CON_MENU, { 'a', -1 }, 1 }, { KEY_CMD_PLIST_CLEAR, "clear_playlist", "Clear the playlist", CON_MENU, { 'C', -1 }, 1 }, { KEY_CMD_PLIST_ADD_DIR, "add_directory", "Add a directory recursively to the playlist", CON_MENU, { 'A', -1 }, 1 }, { KEY_CMD_PLIST_REMOVE_DEAD_ENTRIES, "remove_dead_entries", "Remove playlist entries for non-existent files", CON_MENU, { 'Y', -1 }, 1 }, { KEY_CMD_MIXER_DEC_1, "volume_down_1", "Decrease volume by 1%", CON_MENU, { '<', -1 }, 1 }, { KEY_CMD_MIXER_INC_1, "volume_up_1", "Increase volume by 1%", CON_MENU, { '>', -1 }, 1 }, { KEY_CMD_MIXER_DEC_5, "volume_down_5", "Decrease volume by 5%", CON_MENU, { ',', -1 }, 1 }, { KEY_CMD_MIXER_INC_5, "volume_up_5", "Increase volume by 5%", CON_MENU, { '.', -1 }, 1 }, { KEY_CMD_SEEK_FORWARD, "seek_forward", "Seek forward by n-s", CON_MENU, { KEY_RIGHT, -1 }, 1 }, { KEY_CMD_SEEK_BACKWARD, "seek_backward", "Seek backward by n-s", CON_MENU, { KEY_LEFT, -1}, 1 }, { KEY_CMD_HELP, "help", "Show the help screen", CON_MENU, { 'h', '?', -1 }, 2 }, { KEY_CMD_HIDE_MESSAGE, "hide_message", "Hide error/informative message", CON_MENU, { 'M', -1 }, 1 }, { KEY_CMD_REFRESH, "refresh", "Refresh the screen", CON_MENU, { CTRL('r'), CTRL('l'), -1}, 2 }, { KEY_CMD_RELOAD, "reload", "Reread directory content", CON_MENU, { 'r', -1 }, 1 }, { KEY_CMD_TOGGLE_SHOW_HIDDEN_FILES, "toggle_hidden_files", "Toggle ShowHiddenFiles option", CON_MENU, { 'H', -1 }, 1 }, { KEY_CMD_GO_MUSIC_DIR, "go_to_music_directory", "Go to the music directory (requires config option)", CON_MENU, { 'm', -1 }, 1 }, { KEY_CMD_PLIST_DEL, "delete_from_playlist", "Delete an item from the playlist", CON_MENU, { 'd', -1 }, 1 }, { KEY_CMD_MENU_SEARCH, "search_menu", "Search the menu", CON_MENU, { 'g', '/', -1 }, 2 }, { KEY_CMD_PLIST_SAVE, "save_playlist", "Save the playlist", CON_MENU, { 'V', -1 }, 1 }, { KEY_CMD_TOGGLE_SHOW_TIME, "toggle_show_time", "Toggle ShowTime option", CON_MENU, { CTRL('t'), -1}, 1 }, { KEY_CMD_TOGGLE_SHOW_FORMAT, "toggle_show_format", "Toggle ShowFormat option", CON_MENU, { CTRL('f'), -1 }, 1 }, { KEY_CMD_GO_URL, "go_url", "Play from the URL", CON_MENU, { 'o', -1 }, 1 }, { KEY_CMD_GO_TO_PLAYING_FILE, "go_to_playing_file", "Go to the currently playing file's directory", CON_MENU, { 'G', -1 }, 1 }, { KEY_CMD_GO_DIR, "go_to_a_directory", "Go to a directory", CON_MENU, { 'i', -1 }, 1 }, { KEY_CMD_GO_DIR_UP, "go_up", "Go to '..'", CON_MENU, { 'U', -1 }, 1 }, { KEY_CMD_CANCEL, "cancel", "Exit from an entry", CON_ENTRY, { CTRL('x'), KEY_ESCAPE, -1 }, 2 }, { KEY_CMD_SEEK_FORWARD_5, "seek_forward_fast", "Silent seek forward by 5s", CON_MENU, { ']', -1 }, 1 }, { KEY_CMD_SEEK_BACKWARD_5, "seek_backward_fast", "Silent seek backward by 5s", CON_MENU, { '[', -1 }, 1 }, { KEY_CMD_VOLUME_10, "volume_10", "Set volume to 10%", CON_MENU, { '1' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_20, "volume_20", "Set volume to 20%", CON_MENU, { '2' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_30, "volume_30", "Set volume to 30%", CON_MENU, { '3' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_40, "volume_40", "Set volume to 40%", CON_MENU, { '4' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_50, "volume_50", "Set volume to 50%", CON_MENU, { '5' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_60, "volume_60", "Set volume to 60%", CON_MENU, { '6' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_70, "volume_70", "Set volume to 70%", CON_MENU, { '7' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_80, "volume_80", "Set volume to 80%", CON_MENU, { '8' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_VOLUME_90, "volume_90", "Set volume to 90%", CON_MENU, { '9' | META_KEY_FLAG, -1 }, 1 }, { KEY_CMD_MARK_START, "mark_start", "Mark the start of a block", CON_MENU, { '\'', -1 }, 1 }, { KEY_CMD_MARK_END, "mark_end", "Mark the end of a block", CON_MENU, { '\"', -1 }, 1 }, { KEY_CMD_FAST_DIR_1, "go_to_fast_dir1", "Go to a fast dir 1", CON_MENU, { '!', -1 }, 1 }, { KEY_CMD_FAST_DIR_2, "go_to_fast_dir2", "Go to a fast dir 2", CON_MENU, { '@', -1 }, 1 }, { KEY_CMD_FAST_DIR_3, "go_to_fast_dir3", "Go to a fast dir 3", CON_MENU, { '#', -1 }, 1 }, { KEY_CMD_FAST_DIR_4, "go_to_fast_dir4", "Go to a fast dir 4", CON_MENU, { '$', -1 }, 1 }, { KEY_CMD_FAST_DIR_5, "go_to_fast_dir5", "Go to a fast dir 5", CON_MENU, { '%', -1 }, 1 }, { KEY_CMD_FAST_DIR_6, "go_to_fast_dir6", "Go to a fast dir 6", CON_MENU, { '^', -1 }, 1 }, { KEY_CMD_FAST_DIR_7, "go_to_fast_dir7", "Go to a fast dir 7", CON_MENU, { '&', -1 }, 1 }, { KEY_CMD_FAST_DIR_8, "go_to_fast_dir8", "Go to a fast dir 8", CON_MENU, { '*', -1 }, 1 }, { KEY_CMD_FAST_DIR_9, "go_to_fast_dir9", "Go to a fast dir 9", CON_MENU, { '(', -1 }, 1 }, { KEY_CMD_FAST_DIR_10, "go_to_fast_dir10", "Go to a fast dir 10", CON_MENU, { ')', -1 }, 1 }, { KEY_CMD_HISTORY_UP, "history_up", "Go to the previous entry in the history (entry)", CON_ENTRY, { KEY_UP, -1 }, 1 }, { KEY_CMD_HISTORY_DOWN, "history_down", "Go to the next entry in the history (entry)", CON_ENTRY, { KEY_DOWN, -1 }, 1 }, { KEY_CMD_DELETE_START, "delete_to_start", "Delete to start of line (entry)", CON_ENTRY, { CTRL('u'), -1 }, 1 }, { KEY_CMD_DELETE_END, "delete_to_end", "Delete to end of line (entry)", CON_ENTRY, { CTRL('k'), -1 }, 1 }, { KEY_CMD_TOGGLE_MIXER, "toggle_mixer", "Toggles the mixer channel", CON_MENU, { 'x', -1 }, 1 }, { KEY_CMD_TOGGLE_SOFTMIXER, "toggle_softmixer", "Toggles the software-mixer", CON_MENU, { 'w', -1 }, 1 }, { KEY_CMD_TOGGLE_EQUALIZER, "toggle_equalizer", "Toggles the equalizer", CON_MENU, { 'E', -1 }, 1 }, { KEY_CMD_EQUALIZER_REFRESH, "equalizer_refresh", "Reload EQ-presets", CON_MENU, { 'e', -1 }, 1 }, { KEY_CMD_EQUALIZER_PREV, "equalizer_prev", "Select previous equalizer-preset", CON_MENU, { 'K', -1 }, 1 }, { KEY_CMD_EQUALIZER_NEXT, "equalizer_next", "Select next equalizer-preset", CON_MENU, { 'k', -1 }, 1 }, { KEY_CMD_TOGGLE_MAKE_MONO, "toggle_make_mono", "Toggle mono-mixing", CON_MENU, { 'J', -1 }, 1 }, { KEY_CMD_PLIST_MOVE_UP, "plist_move_up", "Move playlist item up", CON_MENU, { 'u', -1 }, 1 }, { KEY_CMD_PLIST_MOVE_DOWN, "plist_move_down", "Move playlist item down", CON_MENU, { 'j', -1 }, 1 }, { KEY_CMD_ADD_STREAM, "plist_add_stream", "Add a URL to the playlist using entry", CON_MENU, { CTRL('U'), -1 }, 1 }, { KEY_CMD_THEME_MENU, "theme_menu", "Switch to the theme selection menu", CON_MENU, { 'T', -1 }, 1 }, { KEY_CMD_EXEC1, "exec_command1", "Execute ExecCommand1", CON_MENU, { KEY_F(1), -1 }, 1 }, { KEY_CMD_EXEC2, "exec_command2", "Execute ExecCommand2", CON_MENU, { KEY_F(2), -1 }, 1 }, { KEY_CMD_EXEC3, "exec_command3", "Execute ExecCommand3", CON_MENU, { KEY_F(3), -1 }, 1 }, { KEY_CMD_EXEC4, "exec_command4", "Execute ExecCommand4", CON_MENU, { KEY_F(4), -1 }, 1 }, { KEY_CMD_EXEC5, "exec_command5", "Execute ExecCommand5", CON_MENU, { KEY_F(5), -1 }, 1 }, { KEY_CMD_EXEC6, "exec_command6", "Execute ExecCommand6", CON_MENU, { KEY_F(6), -1 }, 1 }, { KEY_CMD_EXEC7, "exec_command7", "Execute ExecCommand7", CON_MENU, { KEY_F(7), -1 }, 1 }, { KEY_CMD_EXEC8, "exec_command8", "Execute ExecCommand8", CON_MENU, { KEY_F(8), -1 }, 1 }, { KEY_CMD_EXEC9, "exec_command9", "Execute ExecCommand9", CON_MENU, { KEY_F(9), -1 }, 1 }, { KEY_CMD_EXEC10, "exec_command10", "Execute ExecCommand10", CON_MENU, { KEY_F(10), -1 }, 1 }, { KEY_CMD_LYRICS, "show_lyrics", "Display lyrics of the current song (if available)", CON_MENU, { 'L', -1 }, 1 }, { KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS, "playlist_full_paths", "Toggle displaying full paths in the playlist", CON_MENU, { 'P', -1 }, 1 }, { KEY_CMD_QUEUE_TOGGLE_FILE, "enqueue_file", "Add (or remove) a file to (from) queue", CON_MENU, { 'z', -1 }, 1 }, { KEY_CMD_QUEUE_CLEAR, "clear_queue", "Clear the queue", CON_MENU, { 'Z', -1 }, 1 } }; static struct special_keys { char *name; int key; } special_keys[] = { { "DOWN", KEY_DOWN }, { "UP", KEY_UP }, { "LEFT", KEY_LEFT }, { "RIGHT", KEY_RIGHT }, { "HOME", KEY_HOME }, { "BACKSPACE", KEY_BACKSPACE }, { "DEL", KEY_DC }, { "INS", KEY_IC }, { "ENTER", '\n' }, { "PAGE_UP", KEY_PPAGE }, { "PAGE_DOWN", KEY_NPAGE }, { "TAB", '\t' }, { "END", KEY_END }, { "KEYPAD_CENTER", KEY_B2 }, { "SPACE", ' ' }, { "ESCAPE", KEY_ESCAPE }, { "F1", KEY_F(1) }, { "F2", KEY_F(2) }, { "F3", KEY_F(3) }, { "F4", KEY_F(4) }, { "F5", KEY_F(5) }, { "F6", KEY_F(6) }, { "F7", KEY_F(7) }, { "F8", KEY_F(8) }, { "F9", KEY_F(9) }, { "F10", KEY_F(10) }, { "F11", KEY_F(11) }, { "F12", KEY_F(12) } }; #define COMMANDS_NUM (ARRAY_SIZE(commands)) #define SPECIAL_KEYS_NUM (ARRAY_SIZE(special_keys)) /* Number of chars from the left where the help message starts * (skipping the key list). */ #define HELP_INDENT 15 static char *help[COMMANDS_NUM]; enum key_cmd get_key_cmd (const enum key_context context, const struct iface_key *key) { int k; size_t i; k = (key->type == IFACE_KEY_CHAR) ? key->key.ucs : key->key.func; for (i = 0; i < COMMANDS_NUM; i += 1) { if (commands[i].context == context) { int j = 0; while (commands[i].keys[j] != -1) { if (commands[i].keys[j++] == k) return commands[i].cmd; } } } return KEY_CMD_WRONG; } /* Return the path to the keymap file or NULL if none was specified. */ static char *find_keymap_file () { char *file; static char path[PATH_MAX]; if ((file = options_get_str("Keymap"))) { if (file[0] == '/') { /* Absolute path */ strncpy (path, file, sizeof(path)); if (path[sizeof(path)-1]) fatal ("Keymap path too long!"); return path; } strncpy (path, create_file_name(file), sizeof(path)); if (path[sizeof(path)-1]) fatal ("Keymap path too long!"); return path; } return NULL; } static void keymap_parse_error (const int line, const char *msg) { fatal ("Parse error in the keymap file line %d: %s", line, msg); } /* Return a key for the symbolic key name (^c, M-F, etc.). * Return -1 on error. */ static int parse_key (const char *symbol) { size_t i; if (strlen(symbol) == 1) { /* Just a regular char */ static bool digit_key_warned = false; if (!digit_key_warned && isdigit (symbol[0])) { fprintf (stderr, "\n\tUsing digits as keys is deprecated as they may" "\n\tbe used for specific purposes in release 2.6.\n"); xsleep (5, 1); digit_key_warned = true; } return symbol[0]; } if (symbol[0] == '^') { /* CTRL sequence */ if (strlen(symbol) != 2) return -1; return CTRL(symbol[1]); } if (!strncasecmp(symbol, "M-", 2)) { /* Meta char */ if (strlen(symbol) != 3) return -1; return symbol[2] | META_KEY_FLAG; } /* Special keys. */ for (i = 0; i < SPECIAL_KEYS_NUM; i += 1) { if (!strcasecmp(special_keys[i].name, symbol)) return special_keys[i].key; } return -1; } /* Remove a single key from the default key definition for a command. */ static void clear_default_key (int key) { size_t cmd_ix; for (cmd_ix = 0; cmd_ix < COMMANDS_NUM; cmd_ix += 1) { int key_ix; for (key_ix = 0; key_ix < commands[cmd_ix].default_keys; key_ix++) { if (commands[cmd_ix].keys[key_ix] == key) break; } if (key_ix == commands[cmd_ix].default_keys) continue; while (commands[cmd_ix].keys[key_ix] != -1) { commands[cmd_ix].keys[key_ix] = commands[cmd_ix].keys[key_ix + 1]; key_ix += 1; } commands[cmd_ix].default_keys -= 1; break; } } /* Remove default keys definition for a command. Return 0 on error. */ static void clear_default_keys (size_t cmd_ix) { assert (cmd_ix < COMMANDS_NUM); commands[cmd_ix].default_keys = 0; commands[cmd_ix].keys[0] = -1; } /* Add a key to the command defined in the keymap file in line * line_num (used only when reporting an error). */ static void add_key (const int line_num, size_t cmd_ix, const char *key_symbol) { int i, key; assert (cmd_ix < COMMANDS_NUM); key = parse_key (key_symbol); if (key == -1) keymap_parse_error (line_num, "bad key sequence"); clear_default_key (key); for (i = commands[cmd_ix].default_keys; commands[cmd_ix].keys[i] != -1; i += 1) { if (commands[cmd_ix].keys[i] == key) return; } if (i == ARRAY_SIZE(commands[cmd_ix].keys) - 1) keymap_parse_error (line_num, "too many keys defined"); commands[cmd_ix].keys[i] = key; commands[cmd_ix].keys[i + 1] = -1; } /* Find command entry by command name; return COMMANDS_NUM if not found. */ static size_t find_command_name (const char *command) { size_t result; for (result = 0; result < COMMANDS_NUM; result += 1) { if (!(strcasecmp(commands[result].name, command))) break; } return result; } /* Load a key map from the file. */ static void load_key_map (const char *file_name) { FILE *file; char *line; int line_num = 0; size_t cmd_ix; if (!(file = fopen(file_name, "r"))) fatal ("Can't open keymap file: %s", xstrerror (errno)); /* Read lines in format: * COMMAND = KEY [KEY ...] * Blank lines and beginning with # are ignored, see example_keymap. */ while ((line = read_line(file))) { char *command, *tmp, *key; line_num++; if (line[0] == '#' || !(command = strtok(line, " \t"))) { /* empty line or a comment */ free (line); continue; } cmd_ix = find_command_name (command); if (cmd_ix == COMMANDS_NUM) keymap_parse_error (line_num, "unknown command"); tmp = strtok(NULL, " \t"); if (!tmp || (strcmp(tmp, "=") && strcmp(tmp, "+="))) keymap_parse_error (line_num, "expected '=' or '+='"); if (strcmp(tmp, "+=")) { if (commands[cmd_ix].keys[commands[cmd_ix].default_keys] != -1) keymap_parse_error (line_num, "command previously bound"); clear_default_keys (cmd_ix); } while ((key = strtok(NULL, " \t"))) add_key (line_num, cmd_ix, key); free (line); } fclose (file); } /* Get a nice key name. * Returned memory may be static. */ static char *get_key_name (const int key) { size_t i; static char key_str[4]; /* Search for special keys */ for (i = 0; i < SPECIAL_KEYS_NUM; i += 1) { if (special_keys[i].key == key) return special_keys[i].name; } /* CTRL combination */ if (!(key & ~CTRL_KEY_CODE)) { key_str[0] = '^'; key_str[1] = key + 0x60; key_str[2] = 0; return key_str; } /* Meta keys */ if (key & META_KEY_FLAG) { strcpy (key_str, "M-"); key_str[2] = key & ~META_KEY_FLAG; key_str[3] = 0; return key_str; } /* Normal key */ key_str[0] = key; key_str[1] = 0; return key_str; } /* Check if keys for cmd1 and cmd2 are different, if not, issue an error. */ static void compare_keys (struct command *cmd1, struct command *cmd2) { int i = 0; while (cmd1->keys[i] != -1) { int j = 0; while (cmd2->keys[j] != -1 && cmd2->keys[j] != cmd1->keys[i]) j++; if (cmd2->keys[j] != -1) fatal ("Key %s is defined for %s and %s!", get_key_name(cmd2->keys[j]), cmd1->name, cmd2->name); i++; } } /* Check that no key is defined more than once. */ static void check_keys () { size_t i, j; for (i = 0; i < COMMANDS_NUM; i += 1) { for (j = 0; j < COMMANDS_NUM; j += 1) { if (j != i && commands[i].context == commands[j].context) compare_keys (&commands[i], &commands[j]); } } } /* Return a string contains the list of keys used for command. * Returned memory is static. */ static char *get_command_keys (const int idx) { static char keys[64]; int i = 0; keys[0] = 0; while (commands[idx].keys[i] != -1) { strncat (keys, get_key_name(commands[idx].keys[i]), sizeof(keys) - strlen(keys) - 1); strncat (keys, " ", sizeof(keys) - strlen(keys) - 1); i++; } /* strip the last space */ if (keys[0] != 0) keys[strlen (keys) - 1] = 0; return keys; } /* Make the help message for keys. */ static void make_help () { size_t i; const char unassigned[] = " [unassigned]"; for (i = 0; i < COMMANDS_NUM; i += 1) { size_t len; len = HELP_INDENT + strlen (commands[i].help) + 1; if (commands[i].keys[0] == -1) len += strlen (unassigned); help[i] = xcalloc (sizeof(char), len); strncpy (help[i], get_command_keys(i), HELP_INDENT); if (strlen(help[i]) < HELP_INDENT) memset (help[i] + strlen(help[i]), ' ', HELP_INDENT - strlen(help[i])); strcpy (help[i] + HELP_INDENT, commands[i].help); if (commands[i].keys[0] == -1) strcat (help[i], unassigned); } } /* Load key map. Set default keys if necessary. */ void keys_init () { char *file = find_keymap_file (); if (file) { load_key_map (file); check_keys (); } make_help (); } /* Free the help message. */ void keys_cleanup () { size_t i; for (i = 0; i < COMMANDS_NUM; i += 1) free (help[i]); } /* Return an array of strings with the keys help. The number of lines is put * in num. */ char **get_keys_help (int *num) { *num = (int) COMMANDS_NUM; return help; } /* Find command entry by key command; return COMMANDS_NUM if not found. */ static size_t find_command_cmd (const enum key_cmd cmd) { size_t result; for (result = 0; result < COMMANDS_NUM; result += 1) { if (commands[result].cmd == cmd) break; } return result; } /* Return true iff the help key is still 'h'. */ bool is_help_still_h () { size_t cmd_ix; cmd_ix = find_command_cmd (KEY_CMD_HELP); assert (cmd_ix < COMMANDS_NUM); return commands[cmd_ix].keys[0] == 'h'; } moc-2.6.0~svn-r3005/keys.h0000664000076400000500000000530412713507747014272 0ustar riesebiesrc#ifndef KEYS_H #define KEYS_H #ifdef __cplusplus extern "C" { #endif enum key_cmd { KEY_CMD_QUIT_CLIENT, KEY_CMD_GO, KEY_CMD_MENU_DOWN, KEY_CMD_MENU_UP, KEY_CMD_MENU_NPAGE, KEY_CMD_MENU_PPAGE, KEY_CMD_MENU_FIRST, KEY_CMD_MENU_LAST, KEY_CMD_QUIT, KEY_CMD_STOP, KEY_CMD_NEXT, KEY_CMD_PREVIOUS, KEY_CMD_PAUSE, KEY_CMD_TOGGLE_READ_TAGS, KEY_CMD_TOGGLE_REPEAT, KEY_CMD_TOGGLE_AUTO_NEXT, KEY_CMD_TOGGLE_MENU, KEY_CMD_TOGGLE_LAYOUT, KEY_CMD_TOGGLE_PERCENT, KEY_CMD_PLIST_ADD_FILE, KEY_CMD_PLIST_CLEAR, KEY_CMD_PLIST_ADD_DIR, KEY_CMD_PLIST_REMOVE_DEAD_ENTRIES, KEY_CMD_MIXER_DEC_1, KEY_CMD_MIXER_INC_1, KEY_CMD_MIXER_DEC_5, KEY_CMD_MIXER_INC_5, KEY_CMD_SEEK_FORWARD, KEY_CMD_SEEK_BACKWARD, KEY_CMD_SEEK_FORWARD_5, KEY_CMD_SEEK_BACKWARD_5, KEY_CMD_HELP, KEY_CMD_HIDE_MESSAGE, KEY_CMD_REFRESH, KEY_CMD_RELOAD, KEY_CMD_TOGGLE_SHOW_HIDDEN_FILES, KEY_CMD_GO_MUSIC_DIR, KEY_CMD_PLIST_DEL, KEY_CMD_MENU_SEARCH, KEY_CMD_PLIST_SAVE, KEY_CMD_TOGGLE_SHOW_FORMAT, KEY_CMD_TOGGLE_SHOW_TIME, KEY_CMD_GO_TO_PLAYING_FILE, KEY_CMD_GO_DIR, KEY_CMD_GO_DIR_UP, KEY_CMD_TOGGLE_SHUFFLE, KEY_CMD_CANCEL, KEY_CMD_GO_URL, KEY_CMD_VOLUME_10, KEY_CMD_VOLUME_20, KEY_CMD_VOLUME_30, KEY_CMD_VOLUME_40, KEY_CMD_VOLUME_50, KEY_CMD_VOLUME_60, KEY_CMD_VOLUME_70, KEY_CMD_VOLUME_80, KEY_CMD_VOLUME_90, KEY_CMD_MARK_START, KEY_CMD_MARK_END, KEY_CMD_FAST_DIR_1, KEY_CMD_FAST_DIR_2, KEY_CMD_FAST_DIR_3, KEY_CMD_FAST_DIR_4, KEY_CMD_FAST_DIR_5, KEY_CMD_FAST_DIR_6, KEY_CMD_FAST_DIR_7, KEY_CMD_FAST_DIR_8, KEY_CMD_FAST_DIR_9, KEY_CMD_FAST_DIR_10, KEY_CMD_TOGGLE_MIXER, KEY_CMD_HISTORY_UP, KEY_CMD_HISTORY_DOWN, KEY_CMD_DELETE_START, KEY_CMD_DELETE_END, KEY_CMD_PLIST_MOVE_UP, KEY_CMD_PLIST_MOVE_DOWN, KEY_CMD_ADD_STREAM, KEY_CMD_THEME_MENU, KEY_CMD_EXEC1, KEY_CMD_EXEC2, KEY_CMD_EXEC3, KEY_CMD_EXEC4, KEY_CMD_EXEC5, KEY_CMD_EXEC6, KEY_CMD_EXEC7, KEY_CMD_EXEC8, KEY_CMD_EXEC9, KEY_CMD_EXEC10, KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS, KEY_CMD_TOGGLE_SOFTMIXER, KEY_CMD_TOGGLE_EQUALIZER, KEY_CMD_EQUALIZER_REFRESH, KEY_CMD_EQUALIZER_PREV, KEY_CMD_EQUALIZER_NEXT, KEY_CMD_TOGGLE_MAKE_MONO, KEY_CMD_LYRICS, KEY_CMD_QUEUE_TOGGLE_FILE, KEY_CMD_QUEUE_CLEAR, KEY_CMD_WRONG }; /* Key context is the place where the user presses a key. A key can have * different meanings in different places. */ enum key_context { CON_MENU, CON_ENTRY_SEARCH, CON_ENTRY }; #ifndef KEY_ESCAPE # define KEY_ESCAPE 27 #endif #define META_KEY_FLAG 0x80 #define CTRL_KEY_CODE 0x1F struct iface_key; enum key_cmd get_key_cmd (const enum key_context context, const struct iface_key *key); void keys_init (); void keys_cleanup (); char **get_keys_help (int *num); bool is_help_still_h (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/lists.c0000664000076400000500000002004213012456773014437 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2009 Damian Pietras and John Fitzgerald * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include "common.h" #include "lists.h" struct lists_strs { int size; /* Number of strings on the list */ int capacity; /* Number of allocated strings */ char **strs; }; /* Allocate a new list of strings and return its address. */ lists_t_strs *lists_strs_new (int reserve) { lists_t_strs *result; assert (reserve >= 0); result = (lists_t_strs *) xmalloc (sizeof (lists_t_strs)); result->size = 0; result->capacity = (reserve ? reserve : 64); result->strs = (char **) xcalloc (sizeof (char *), result->capacity); return result; } /* Clear a list to an empty state. */ void lists_strs_clear (lists_t_strs *list) { int ix; assert (list); for (ix = 0; ix < list->size; ix += 1) free ((void *) list->strs[ix]); list->size = 0; } /* Free all storage associated with a list of strings. */ void lists_strs_free (lists_t_strs *list) { assert (list); lists_strs_clear (list); free (list->strs); free (list); } /* Return the number of strings in a list. */ int lists_strs_size (const lists_t_strs *list) { assert (list); return list->size; } /* Return the total number of strings which could be held without growing. */ int lists_strs_capacity (const lists_t_strs *list) { assert (list); return list->capacity; } /* Return true iff the list has no members. */ bool lists_strs_empty (const lists_t_strs *list) { assert (list); return list->size == 0 ? true : false; } /* Given an index, return the string at that position in a list. */ char *lists_strs_at (const lists_t_strs *list, int index) { assert (list); assert (LIMIT(index, list->size)); return list->strs[index]; } /* Sort string list into an order determined by caller's comparitor. */ void lists_strs_sort (lists_t_strs *list, lists_t_compare *compare) { assert (list); assert (compare); qsort (list->strs, list->size, sizeof (char *), compare); } /* Reverse the order of entries in a list. */ void lists_strs_reverse (lists_t_strs *list) { int ix, iy; assert (list); for (ix = 0, iy = list->size - 1; ix < iy; ix += 1, iy -= 1) { char *str; str = list->strs[ix]; list->strs[ix] = list->strs[iy]; list->strs[iy] = str; } } /* Take a string and push it onto the end of a list * (expanding the list if necessary). */ void lists_strs_push (lists_t_strs *list, char *s) { assert (list); assert (s); if (list->size == list->capacity) { list->capacity *= 2; list->strs = (char **) xrealloc (list->strs, list->capacity * sizeof (char *)); } list->strs[list->size] = s; list->size += 1; } /* Remove the last string on the list and return it, or NULL if the list * is empty. */ char *lists_strs_pop (lists_t_strs *list) { char *result; assert (list); result = NULL; if (list->size > 0) { list->size -= 1; result = list->strs[list->size]; } return result; } /* Replace the nominated string with a new one and return the old one. */ char *lists_strs_swap (lists_t_strs *list, int index, char *s) { char *result; assert (list); assert (LIMIT(index, list->size)); assert (s); result = list->strs[index]; list->strs[index] = s; return result; } /* Copy a string and append it to the end of a list. */ void lists_strs_append (lists_t_strs *list, const char *s) { char *str; assert (list); assert (s); str = xstrdup (s); lists_strs_push (list, str); } /* Remove a string from the end of the list and free it. */ void lists_strs_remove (lists_t_strs *list) { char *str; assert (list); str = lists_strs_pop (list); if (str) free (str); } /* Replace the nominated string with a copy of the new one * and free the old one. */ void lists_strs_replace (lists_t_strs *list, int index, char *s) { char *str; assert (list); assert (LIMIT(index, list->size)); str = xstrdup (s); str = lists_strs_swap (list, index, str); free (str); } /* Split a string at any delimiter in given string. The resulting segments * are appended to the given string list. Returns the number of tokens * appended. */ int lists_strs_split (lists_t_strs *list, const char *s, const char *delim) { int result; char *str, *token, *saveptr; assert (list); assert (s); assert (delim); result = 0; str = xstrdup (s); token = strtok_r (str, delim, &saveptr); while (token) { result += 1; lists_strs_append (list, token); token = strtok_r (NULL, delim, &saveptr); } free (str); return result; } /* Tokenise a string and append the tokens to the list. * Returns the number of tokens appended. */ int lists_strs_tokenise (lists_t_strs *list, const char *s) { int result; assert (list); assert (s); result = lists_strs_split (list, s, " \t"); return result; } /* Return the concatenation of all the strings in a list using the * given format for each, or NULL if the list is empty. */ GCC_DIAG_OFF(format-nonliteral) char *lists_strs_fmt (const lists_t_strs *list, const char *fmt) { int len, ix, rc; char *result, *ptr; assert (list); assert (strstr (fmt, "%s")); result = NULL; if (!lists_strs_empty (list)) { len = 0; for (ix = 0; ix < lists_strs_size (list); ix += 1) len += strlen (lists_strs_at (list, ix)); len += ix * (strlen (fmt) - 2); ptr = result = xmalloc (len + 1); for (ix = 0; ix < lists_strs_size (list); ix += 1) { rc = snprintf (ptr, len + 1, fmt, lists_strs_at (list, ix)); if (rc > len) fatal ("Allocated string area was too small!"); len -= rc; ptr += rc; } } return result; } GCC_DIAG_ON(format-nonliteral) /* Return the concatenation of all the strings in a list, or NULL * if the list is empty. */ char *lists_strs_cat (const lists_t_strs *list) { char *result; assert (list); result = lists_strs_fmt (list, "%s"); return result; } /* Return a "snapshot" of the given string list. The returned memory is a * null-terminated list of pointers to the given list's strings copied into * memory allocated after the pointer list. This list is suitable for passing * to functions which take such a list as an argument (e.g., execv()). * Invoking free() on the returned pointer also frees the strings. */ char **lists_strs_save (const lists_t_strs *list) { int ix, size; char *ptr, **result; assert (list); size = 0; for (ix = 0; ix < lists_strs_size (list); ix += 1) size += strlen (lists_strs_at (list, ix)) + 1; size += sizeof (char *) * (lists_strs_size (list) + 1); result = (char **) xmalloc (size); ptr = (char *) (result + lists_strs_size (list) + 1); for (ix = 0; ix < lists_strs_size (list); ix += 1) { strcpy (ptr, lists_strs_at (list, ix)); result[ix] = ptr; ptr += strlen (ptr) + 1; } result[ix] = NULL; return result; } /* Reload saved strings into a list. The reloaded strings are appended * to the list. The number of items reloaded is returned. */ int lists_strs_load (lists_t_strs *list, const char **saved) { int size; assert (list); assert (saved); size = lists_strs_size (list); while (*saved) lists_strs_append (list, *saved++); return lists_strs_size (list) - size; } /* Given a string, return the index of the first list entry which matches * it. If not found, return the total number of entries. * The comparison is case-insensitive. */ int lists_strs_find (lists_t_strs *list, const char *sought) { int result; assert (list); assert (sought); for (result = 0; result < lists_strs_size (list); result += 1) { if (!strcasecmp (lists_strs_at (list, result), sought)) break; } return result; } /* Given a string, return true iff it exists in the list. */ bool lists_strs_exists (lists_t_strs *list, const char *sought) { bool result = false; assert (list); assert (sought); if (lists_strs_find (list, sought) < lists_strs_size (list)) result = true; return result; } moc-2.6.0~svn-r3005/lists.h0000664000076400000500000000326712424322426014447 0ustar riesebiesrc#ifndef LISTS_H #define LISTS_H #include "common.h" #ifdef __cplusplus extern "C" { #endif typedef struct lists_strs lists_t_strs; typedef int lists_t_compare (const void *, const void *); /* List administration functions. */ lists_t_strs *lists_strs_new (int reserve); void lists_strs_clear (lists_t_strs *list); void lists_strs_free (lists_t_strs *list); int lists_strs_size (const lists_t_strs *list); int lists_strs_capacity (const lists_t_strs *list); bool lists_strs_empty (const lists_t_strs *list); /* List member access functions. */ char *lists_strs_at (const lists_t_strs *list, int index); /* List mutating functions. */ void lists_strs_sort (lists_t_strs *list, lists_t_compare *compare); void lists_strs_reverse (lists_t_strs *list); /* Ownership transferring functions. */ void lists_strs_push (lists_t_strs *list, char *s); char *lists_strs_pop (lists_t_strs *list); char *lists_strs_swap (lists_t_strs *list, int index, char *s); /* Ownership preserving functions. */ void lists_strs_append (lists_t_strs *list, const char *s); void lists_strs_remove (lists_t_strs *list); void lists_strs_replace (lists_t_strs *list, int index, char *s); /* Helper functions. */ int lists_strs_split (lists_t_strs *list, const char *s, const char *delim); int lists_strs_tokenise (lists_t_strs *list, const char *s); char *lists_strs_fmt (const lists_t_strs *list, const char *fmt); char *lists_strs_cat (const lists_t_strs *list); char **lists_strs_save (const lists_t_strs *list); int lists_strs_load (lists_t_strs *list, const char **saved); int lists_strs_find (lists_t_strs *list, const char *sought); bool lists_strs_exists (lists_t_strs *list, const char *sought); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/log.c0000664000076400000500000001635513022625703014066 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include "common.h" #include "lists.h" #include "log.h" #include "options.h" #ifndef NDEBUG static FILE *logfp = NULL; /* logging file stream */ static enum { UNINITIALISED, BUFFERING, LOGGING } logging_state = UNINITIALISED; static lists_t_strs *buffered_log = NULL; static int log_records_spilt = 0; static lists_t_strs *circular_log = NULL; static int circular_ptr = 0; static pthread_mutex_t logging_mtx = PTHREAD_MUTEX_INITIALIZER; static struct { int sig; const char *name; volatile uint64_t raised; uint64_t logged; } sig_info[] = { {SIGINT, "SIGINT", 0, 0}, {SIGHUP, "SIGHUP", 0, 0}, {SIGQUIT, "SIGQUIT", 0, 0}, {SIGTERM, "SIGTERM", 0, 0}, {SIGCHLD, "SIGCHLD", 0, 0}, #ifdef SIGWINCH {SIGWINCH, "SIGWINCH", 0, 0}, #endif {0, "SIG other", 0, 0} }; #endif #ifndef NDEBUG void log_signal (int sig) { int ix = 0; while (sig_info[ix].sig && sig_info[ix].sig != sig) ix += 1; sig_info[ix].raised += 1; } #endif #ifndef NDEBUG static inline void flush_log (void) { int rc; if (logfp) { do { rc = fflush (logfp); } while (rc != 0 && errno == EINTR); } } #endif #ifndef NDEBUG static void locked_logit (const char *file, const int line, const char *function, const char *msg) { int len; char *str, time_str[20]; struct timespec utc_time; time_t tv_sec; struct tm tm_time; const char fmt[] = "%s.%06ld: %s:%d %s(): %s\n"; assert (logging_state == BUFFERING || logging_state == LOGGING); assert (logging_state != BUFFERING || !logfp); assert (logging_state != BUFFERING || !circular_log); assert (logging_state != LOGGING || logfp || !circular_log); if (logging_state == LOGGING && !logfp) return; get_realtime (&utc_time); tv_sec = utc_time.tv_sec; localtime_r (&tv_sec, &tm_time); strftime (time_str, sizeof (time_str), "%b %e %T", &tm_time); if (logfp && !circular_log) { fprintf (logfp, fmt, time_str, utc_time.tv_nsec / 1000L, file, line, function, msg); return; } len = snprintf (NULL, 0, fmt, time_str, utc_time.tv_nsec / 1000L, file, line, function, msg); str = xmalloc (len + 1); snprintf (str, len + 1, fmt, time_str, utc_time.tv_nsec / 1000L, file, line, function, msg); if (logging_state == BUFFERING) { lists_strs_push (buffered_log, str); return; } assert (circular_log); if (circular_ptr == lists_strs_capacity (circular_log)) circular_ptr = 0; if (circular_ptr < lists_strs_size (circular_log)) free (lists_strs_swap (circular_log, circular_ptr, str)); else lists_strs_push (circular_log, str); circular_ptr += 1; } #endif #ifndef NDEBUG static void log_signals_raised (void) { size_t ix; for (ix = 0; ix < ARRAY_SIZE(sig_info); ix += 1) { while (sig_info[ix].raised > sig_info[ix].logged) { locked_logit (__FILE__, __LINE__, __func__, sig_info[ix].name); sig_info[ix].logged += 1; } } } #endif /* Put something into the log. If built with logging disabled, * this function is provided as a stub so independant plug-ins * configured with logging enabled can still resolve it. */ void internal_logit (const char *file LOGIT_ONLY, const int line LOGIT_ONLY, const char *function LOGIT_ONLY, const char *format LOGIT_ONLY, ...) { #ifndef NDEBUG int saved_errno = errno; char *msg; va_list va; LOCK(logging_mtx); if (!logfp) { switch (logging_state) { case UNINITIALISED: buffered_log = lists_strs_new (128); logging_state = BUFFERING; break; case BUFFERING: /* Don't let storage run away on us. */ if (lists_strs_size (buffered_log) < lists_strs_capacity (buffered_log)) break; log_records_spilt += 1; case LOGGING: goto end; } } log_signals_raised (); va_start (va, format); msg = format_msg_va (format, va); va_end (va); locked_logit (file, line, function, msg); free (msg); flush_log (); log_signals_raised (); end: UNLOCK(logging_mtx); errno = saved_errno; #endif } /* Initialize logging stream */ void log_init_stream (FILE *f LOGIT_ONLY, const char *fn LOGIT_ONLY) { #ifndef NDEBUG char *msg; LOCK(logging_mtx); logfp = f; if (logging_state == BUFFERING) { if (logfp) { int ix; for (ix = 0; ix < lists_strs_size (buffered_log); ix += 1) fprintf (logfp, "%s", lists_strs_at (buffered_log, ix)); } lists_strs_free (buffered_log); buffered_log = NULL; } logging_state = LOGGING; if (!logfp) goto end; msg = format_msg ("Writing log to: %s", fn); locked_logit (__FILE__, __LINE__, __func__, msg); free (msg); if (log_records_spilt > 0) { msg = format_msg ("%d log records spilt", log_records_spilt); locked_logit (__FILE__, __LINE__, __func__, msg); free (msg); } flush_log (); end: UNLOCK(logging_mtx); #endif } /* Start circular logging (if enabled). */ void log_circular_start () { #ifndef NDEBUG int circular_size; assert (logging_state == LOGGING); assert (!circular_log); if (!logfp) return; circular_size = options_get_int ("CircularLogSize"); if (circular_size > 0) { LOCK(logging_mtx); circular_log = lists_strs_new (circular_size); circular_ptr = 0; UNLOCK(logging_mtx); } #endif } /* Internal circular log reset. */ #ifndef NDEBUG static inline void locked_circular_reset () { lists_strs_clear (circular_log); circular_ptr = 0; } #endif /* Reset the circular log (if enabled). */ void log_circular_reset () { #ifndef NDEBUG assert (logging_state == LOGGING); if (!circular_log) return; LOCK(logging_mtx); locked_circular_reset (); UNLOCK(logging_mtx); #endif } /* Write circular log (if enabled) to the log file. */ void log_circular_log () { #ifndef NDEBUG int ix; assert (logging_state == LOGGING && (logfp || !circular_log)); if (!circular_log) return; LOCK(logging_mtx); fprintf (logfp, "\n* Circular Log Starts *\n\n"); for (ix = circular_ptr; ix < lists_strs_size (circular_log); ix += 1) fprintf (logfp, "%s", lists_strs_at (circular_log, ix)); fflush (logfp); for (ix = 0; ix < circular_ptr; ix += 1) fprintf (logfp, "%s", lists_strs_at (circular_log, ix)); fprintf (logfp, "\n* Circular Log Ends *\n\n"); fflush (logfp); locked_circular_reset (); UNLOCK(logging_mtx); #endif } /* Stop circular logging (if enabled). */ void log_circular_stop () { #ifndef NDEBUG assert (logging_state == LOGGING); if (!circular_log) return; LOCK(logging_mtx); lists_strs_free (circular_log); circular_log = NULL; circular_ptr = 0; UNLOCK(logging_mtx); #endif } void log_close () { #ifndef NDEBUG LOCK(logging_mtx); if (!(logfp == stdout || logfp == stderr || logfp == NULL)) { fclose (logfp); logfp = NULL; } if (buffered_log) { lists_strs_free (buffered_log); buffered_log = NULL; } log_records_spilt = 0; UNLOCK(logging_mtx); #endif } moc-2.6.0~svn-r3005/log.h0000664000076400000500000000265613333136274014077 0ustar riesebiesrc#ifndef LOG_H #define LOG_H #include #ifdef __cplusplus extern "C" { #endif /* Suppress overly-enthusiastic GNU variadic macro extensions warning. */ #if defined(__clang__) && HAVE_VARIADIC_MACRO_WARNING # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #endif #ifdef DEBUG # define debug logit #else # define debug(...) do {} while (0) #endif #ifndef NDEBUG # define logit(...) \ internal_logit (__FILE__, __LINE__, __func__, ## __VA_ARGS__) #else # define logit(...) do {} while (0) #endif #ifndef STRERROR_FN # define STRERROR_FN xstrerror #endif #ifndef NDEBUG #define log_errno(format, errnum) \ do { \ char *err##__LINE__ = STRERROR_FN (errnum); \ logit (format ": %s", err##__LINE__); \ free (err##__LINE__); \ } while (0) #else # define log_errno(...) do {} while (0) #endif void internal_logit (const char *file, const int line, const char *function, const char *format, ...) ATTR_PRINTF(4, 5); #ifndef NDEBUG # define LOGIT_ONLY #else # define LOGIT_ONLY ATTR_UNUSED #endif #if !defined(NDEBUG) && defined(DEBUG) # define DEBUG_ONLY #else # define DEBUG_ONLY ATTR_UNUSED #endif void log_init_stream (FILE *f, const char *fn); void log_circular_start (); void log_circular_log (); void log_circular_reset (); void log_circular_stop (); void log_close (); #ifndef NDEBUG void log_signal (int sig); #else # define log_signal(...) do {} while (0) #endif #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/lyrics.c0000664000076400000500000001265112661770467014625 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2008-2009 Geraud Le Falher and John Fitzgerald * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "common.h" #include "files.h" #include "log.h" #include "options.h" #include "lists.h" #include "lyrics.h" static lists_t_strs *raw_lyrics = NULL; static const char *lyrics_message = NULL; /* Return the list of lyrics lines, or NULL if none are loaded. */ lists_t_strs *lyrics_lines_get (void) { return raw_lyrics; } /* Store new lyrics lines as supplied. */ void lyrics_lines_set (lists_t_strs *lines) { assert (!raw_lyrics); assert (lines); raw_lyrics = lines; lyrics_message = NULL; } /* Return a list of lyrics lines loaded from a file, or NULL on error. */ lists_t_strs *lyrics_load_file (const char *filename) { int text_plain; FILE *lyrics_file = NULL; char *mime, *line; lists_t_strs *result; assert (filename); lyrics_message = "[No lyrics file!]"; if (!file_exists (filename)) return NULL; mime = file_mime_type (filename); text_plain = mime ? !strncmp (mime, "text/plain", 10) : 0; free (mime); if (!text_plain) return NULL; lyrics_file = fopen (filename, "r"); if (lyrics_file == NULL) { char *err = xstrerror (errno); logit ("Error reading '%s': %s", filename, err); free (err); lyrics_message = "[Lyrics file cannot be read!]"; return NULL; } result = lists_strs_new (0); while ((line = read_line (lyrics_file)) != NULL) lists_strs_push (result, line); fclose (lyrics_file); lyrics_message = NULL; return result; } /* Given an audio's file name, load lyrics from the default lyrics file name. */ void lyrics_autoload (const char *filename) { char *lyrics_filename, *extn; assert (!raw_lyrics); assert (lyrics_message); if (filename == NULL) { lyrics_message = "[No file playing!]"; return; } if (!options_get_bool ("AutoLoadLyrics")) { lyrics_message = "[Lyrics not autoloaded!]"; return; } if (is_url (filename)) { lyrics_message = "[Lyrics from URL is not supported!]"; return; } lyrics_filename = xstrdup (filename); extn = ext_pos (lyrics_filename); if (extn) { *--extn = '\0'; raw_lyrics = lyrics_load_file (lyrics_filename); } else lyrics_message = "[No lyrics file!]"; free (lyrics_filename); } /* Given a line, return a centred copy of it. */ static char *centre_line (const char* line, int max) { char *result; int len; len = strlen (line); if (len < (max - 1)) { int space; space = (max - len) / 2; result = (char *) xmalloc (space + len + 2); memset (result, ' ', space); strcpy (&result[space], line); len += space; } else { result = (char *) xmalloc (max + 2); strncpy (result, line, max); len = max; } strcpy (&result[len], "\n"); return result; } /* Centre all the lines in the lyrics. */ static lists_t_strs *centre_style (lists_t_strs *lines, int unused1 ATTR_UNUSED, int width, void *unused2 ATTR_UNUSED) { lists_t_strs *result; int ix, size; size = lists_strs_size (lines); result = lists_strs_new (size); for (ix = 0; ix < size; ix += 1) { char *old_line, *new_line; old_line = lists_strs_at (lines, ix); new_line = centre_line (old_line, width); lists_strs_push (result, new_line); } return result; } /* Formatting function information. */ static lyrics_t_formatter *lyrics_formatter = centre_style; static lyrics_t_reaper *formatter_reaper = NULL; static void *formatter_data = NULL; /* Register a new function to be used for formatting. A NULL formatter * resets formatting to the default centred style. */ void lyrics_use_formatter (lyrics_t_formatter formatter, lyrics_t_reaper reaper, void *data) { if (formatter_reaper) formatter_reaper (formatter_data); if (formatter) { lyrics_formatter = formatter; formatter_reaper = reaper; formatter_data = data; } else { lyrics_formatter = centre_style; formatter_reaper = NULL; formatter_data = NULL; } } /* Return a list of either the formatted lyrics if any are loaded or * a centred message. */ lists_t_strs *lyrics_format (int height, int width) { int ix; lists_t_strs *result; assert (raw_lyrics || lyrics_message); result = NULL; if (raw_lyrics) { result = lyrics_formatter (raw_lyrics, height, width - 1, formatter_data); if (!result) lyrics_message = "[Error formatting lyrics!]"; } if (!result) { char *line; result = lists_strs_new (1); line = centre_line (lyrics_message, width - 1); lists_strs_push (result, line); } for (ix = 0; ix < lists_strs_size (result); ix += 1) { int len; char *this_line; this_line = lists_strs_at (result, ix); len = strlen (this_line); if (len > width - 1) strcpy (&this_line[width - 1], "\n"); else if (this_line[len - 1] != '\n') { char *new_line; new_line = xmalloc (len + 2); strcpy (new_line, this_line); strcat (new_line, "\n"); lists_strs_swap (result, ix, new_line); free (this_line); } } return result; } /* Dispose of raw lyrics lines. */ void lyrics_cleanup (void) { if (raw_lyrics) { lists_strs_free (raw_lyrics); raw_lyrics = NULL; } lyrics_message = "[No lyrics loaded!]"; } moc-2.6.0~svn-r3005/lyrics.h0000664000076400000500000000112711322047704014606 0ustar riesebiesrc#ifndef LYRICS_H #define LYRICS_H #ifdef __cplusplus extern "C" { #endif typedef lists_t_strs *lyrics_t_formatter (lists_t_strs *lines, int height, int width, void *data); typedef void lyrics_t_reaper (void *data); lists_t_strs *lyrics_lines_get (void); void lyrics_lines_set (lists_t_strs *lines); lists_t_strs *lyrics_load_file (const char *filename); void lyrics_autoload (const char *filename); void lyrics_use_formatter (lyrics_t_formatter, lyrics_t_reaper, void *data); lists_t_strs *lyrics_format (int height, int width); void lyrics_cleanup (void); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/m4/0002775000076400000500000000000013710016723013451 5ustar riesebiesrcmoc-2.6.0~svn-r3005/m4/ax_c___attribute__.m40000664000076400000500000000476712404420426017516 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_c___attribute__.html # =========================================================================== # # SYNOPSIS # # AX_C___ATTRIBUTE__ # # DESCRIPTION # # Provides a test for the compiler support of __attribute__ extensions. # Defines HAVE___ATTRIBUTE__ if it is found. # # LICENSE # # Copyright (c) 2008 Stepan Kasal # Copyright (c) 2008 Christian Haggstrom # Copyright (c) 2008 Ryan McCabe # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 8 AC_DEFUN([AX_C___ATTRIBUTE__], [ AC_CACHE_CHECK([for __attribute__], [ax_cv___attribute__], [AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [[#include static void foo(void) __attribute__ ((unused)); static void foo(void) { exit(1); } ]], [])], [ax_cv___attribute__=yes], [ax_cv___attribute__=no] ) ]) if test "$ax_cv___attribute__" = "yes"; then AC_DEFINE([HAVE___ATTRIBUTE__], 1, [define if your compiler has __attribute__]) fi ]) moc-2.6.0~svn-r3005/m4/ax_cflags_gcc_option.m40000664000076400000500000001515310101663703020046 0ustar riesebiesrcdnl @synopsis AX_CFLAGS_GCC_OPTION (optionflag [,[shellvar][,[A][,[NA]]]) dnl dnl AX_CFLAGS_GCC_OPTION(-fvomit-frame) would show a message as like dnl "checking CFLAGS for gcc -fvomit-frame ... yes" and adds dnl the optionflag to CFLAGS if it is understood. You can override dnl the shellvar-default of CFLAGS of course. The order of arguments dnl stems from the explicit macros like AX_CFLAGS_WARN_ALL. dnl dnl The cousin AX_CXXFLAGS_GCC_OPTION would check for an option to add dnl to CXXFLAGS - and it uses the autoconf setup for C++ instead of C dnl (since it is possible to use different compilers for C and C++). dnl dnl The macro is a lot simpler than any special AX_CFLAGS_* macro (or dnl ac_cxx_rtti.m4 macro) but allows to check for arbitrary options. dnl However, if you use this macro in a few places, it would be great dnl if you would make up a new function-macro and submit it to the dnl ac-archive. dnl dnl - $1 option-to-check-for : required ("-option" as non-value) dnl - $2 shell-variable-to-add-to : CFLAGS (or CXXFLAGS in the other case) dnl - $3 action-if-found : add value to shellvariable dnl - $4 action-if-not-found : nothing dnl dnl note: in earlier versions, $1-$2 were swapped. We try to detect the dnl situation and accept a $2=~/-/ as being the old option-to-check-for. dnl dnl also: there are other variants that emerged from the original macro dnl variant which did just test an option to be possibly added. However, dnl some compilers accept an option silently, or possibly for just dnl another option that was not intended. Therefore, we have to do a dnl generic test for a compiler family. For gcc we check "-pedantic" dnl being accepted which is also understood by compilers who just want dnl to be compatible with gcc even when not being made from gcc sources. dnl dnl see also: dnl AX_CFLAGS_SUN_OPTION AX_CFLAGS_HPUX_OPTION dnl AX_CFLAGS_AIX_OPTION AX_CFLAGS_IRIX_OPTION dnl dnl @, tested, experimental dnl @version $Id: ax_cflags_gcc_option.m4,v 1.5 2003/11/29 08:13:25 guidod Exp $ dnl @author Guido Draheim dnl AC_DEFUN([AX_CFLAGS_GCC_OPTION_OLD], [dnl AS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl AS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$2])dnl AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for gcc m4_ifval($2,$2,-option)], VAR,[VAR="no, unknown" AC_LANG_SAVE AC_LANG_C ac_save_[]FLAGS="$[]FLAGS" for ac_arg dnl in "-pedantic % m4_ifval($2,$2,-option)" dnl GCC # do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` AC_TRY_COMPILE([],[return 0;], [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) done FLAGS="$ac_save_[]FLAGS" AC_LANG_RESTORE ]) case ".$VAR" in .ok|.ok,*) m4_ifvaln($3,$3) ;; .|.no|.no,*) m4_ifvaln($4,$4) ;; *) m4_ifvaln($3,$3,[ if echo " $[]m4_ifval($1,$1,FLAGS) " | grep " $VAR " 2>&1 >/dev/null then AC_RUN_LOG([: m4_ifval($1,$1,FLAGS) does contain $VAR]) else AC_RUN_LOG([: m4_ifval($1,$1,FLAGS)="$m4_ifval($1,$1,FLAGS) $VAR"]) m4_ifval($1,$1,FLAGS)="$m4_ifval($1,$1,FLAGS) $VAR" fi ]) ;; esac AS_VAR_POPDEF([VAR])dnl AS_VAR_POPDEF([FLAGS])dnl ]) dnl the only difference - the LANG selection... and the default FLAGS AC_DEFUN([AX_CXXFLAGS_GCC_OPTION_OLD], [dnl AS_VAR_PUSHDEF([FLAGS],[CXXFLAGS])dnl AS_VAR_PUSHDEF([VAR],[ac_cv_cxxflags_gcc_option_$2])dnl AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for gcc m4_ifval($2,$2,-option)], VAR,[VAR="no, unknown" AC_LANG_SAVE AC_LANG_CXX ac_save_[]FLAGS="$[]FLAGS" for ac_arg dnl in "-pedantic % m4_ifval($2,$2,-option)" dnl GCC # do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` AC_TRY_COMPILE([],[return 0;], [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) done FLAGS="$ac_save_[]FLAGS" AC_LANG_RESTORE ]) case ".$VAR" in .ok|.ok,*) m4_ifvaln($3,$3) ;; .|.no|.no,*) m4_ifvaln($4,$4) ;; *) m4_ifvaln($3,$3,[ if echo " $[]m4_ifval($1,$1,FLAGS) " | grep " $VAR " 2>&1 >/dev/null then AC_RUN_LOG([: m4_ifval($1,$1,FLAGS) does contain $VAR]) else AC_RUN_LOG([: m4_ifval($1,$1,FLAGS)="$m4_ifval($1,$1,FLAGS) $VAR"]) m4_ifval($1,$1,FLAGS)="$m4_ifval($1,$1,FLAGS) $VAR" fi ]) ;; esac AS_VAR_POPDEF([VAR])dnl AS_VAR_POPDEF([FLAGS])dnl ]) dnl ------------------------------------------------------------------------- AC_DEFUN([AX_CFLAGS_GCC_OPTION_NEW], [dnl AS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl AS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$1])dnl AC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)], VAR,[VAR="no, unknown" AC_LANG_SAVE AC_LANG_C ac_save_[]FLAGS="$[]FLAGS" for ac_arg dnl in "-pedantic % m4_ifval($1,$1,-option)" dnl GCC # do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` AC_TRY_COMPILE([],[return 0;], [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) done FLAGS="$ac_save_[]FLAGS" AC_LANG_RESTORE ]) case ".$VAR" in .ok|.ok,*) m4_ifvaln($3,$3) ;; .|.no|.no,*) m4_ifvaln($4,$4) ;; *) m4_ifvaln($3,$3,[ if echo " $[]m4_ifval($2,$2,FLAGS) " | grep " $VAR " 2>&1 >/dev/null then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR]) else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR"]) m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR" fi ]) ;; esac AS_VAR_POPDEF([VAR])dnl AS_VAR_POPDEF([FLAGS])dnl ]) dnl the only difference - the LANG selection... and the default FLAGS AC_DEFUN([AX_CXXFLAGS_GCC_OPTION_NEW], [dnl AS_VAR_PUSHDEF([FLAGS],[CXXFLAGS])dnl AS_VAR_PUSHDEF([VAR],[ac_cv_cxxflags_gcc_option_$1])dnl AC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)], VAR,[VAR="no, unknown" AC_LANG_SAVE AC_LANG_CXX ac_save_[]FLAGS="$[]FLAGS" for ac_arg dnl in "-pedantic % m4_ifval($1,$1,-option)" dnl GCC # do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` AC_TRY_COMPILE([],[return 0;], [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) done FLAGS="$ac_save_[]FLAGS" AC_LANG_RESTORE ]) case ".$VAR" in .ok|.ok,*) m4_ifvaln($3,$3) ;; .|.no|.no,*) m4_ifvaln($4,$4) ;; *) m4_ifvaln($3,$3,[ if echo " $[]m4_ifval($2,$2,FLAGS) " | grep " $VAR " 2>&1 >/dev/null then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR]) else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR"]) m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR" fi ]) ;; esac AS_VAR_POPDEF([VAR])dnl AS_VAR_POPDEF([FLAGS])dnl ]) AC_DEFUN([AX_CFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1, [AX_CFLAGS_GCC_OPTION_NEW($@)],[AX_CFLAGS_GCC_OPTION_OLD($@)])]) AC_DEFUN([AX_CXXFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1, [AX_CXXFLAGS_GCC_OPTION_NEW($@)],[AX_CXXFLAGS_GCC_OPTION_OLD($@)])]) moc-2.6.0~svn-r3005/m4/ax_compare_version.m40000664000076400000500000001465212404420402017574 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_compare_version.html # =========================================================================== # # SYNOPSIS # # AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) # # DESCRIPTION # # This macro compares two version strings. Due to the various number of # minor-version numbers that can exist, and the fact that string # comparisons are not compatible with numeric comparisons, this is not # necessarily trivial to do in a autoconf script. This macro makes doing # these comparisons easy. # # The six basic comparisons are available, as well as checking equality # limited to a certain number of minor-version levels. # # The operator OP determines what type of comparison to do, and can be one # of: # # eq - equal (test A == B) # ne - not equal (test A != B) # le - less than or equal (test A <= B) # ge - greater than or equal (test A >= B) # lt - less than (test A < B) # gt - greater than (test A > B) # # Additionally, the eq and ne operator can have a number after it to limit # the test to that number of minor versions. # # eq0 - equal up to the length of the shorter version # ne0 - not equal up to the length of the shorter version # eqN - equal up to N sub-version levels # neN - not equal up to N sub-version levels # # When the condition is true, shell commands ACTION-IF-TRUE are run, # otherwise shell commands ACTION-IF-FALSE are run. The environment # variable 'ax_compare_version' is always set to either 'true' or 'false' # as well. # # Examples: # # AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) # AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) # # would both be true. # # AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) # AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) # # would both be false. # # AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) # # would be true because it is only comparing two minor versions. # # AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) # # would be true because it is only comparing the lesser number of minor # versions of the two values. # # Note: The characters that separate the version numbers do not matter. An # empty string is the same as version 0. OP is evaluated by autoconf, not # configure, so must be a string, not a variable. # # The author would like to acknowledge Guido Draheim whose advice about # the m4_case and m4_ifvaln functions make this macro only include the # portions necessary to perform the specific comparison specified by the # OP argument in the final configure script. # # LICENSE # # Copyright (c) 2008 Tim Toolan # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ AC_REQUIRE([AC_PROG_AWK]) # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [illegal OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([illegal OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION moc-2.6.0~svn-r3005/m4/ax_gcc_func_attribute.m40000664000076400000500000001655512404420426020245 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html # =========================================================================== # # SYNOPSIS # # AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE) # # DESCRIPTION # # This macro checks if the compiler supports one of GCC's function # attributes; many other compilers also provide function attributes with # the same syntax. Compiler warnings are used to detect supported # attributes as unsupported ones are ignored by default so quieting # warnings when using this macro will yield false positives. # # The ATTRIBUTE parameter holds the name of the attribute to be checked. # # If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_. # # The macro caches its result in the ax_cv_have_func_attribute_ # variable. # # The macro currently supports the following function attributes: # # alias # aligned # alloc_size # always_inline # artificial # cold # const # constructor # deprecated # destructor # dllexport # dllimport # error # externally_visible # flatten # format # format_arg # gnu_inline # hot # ifunc # leaf # malloc # noclone # noinline # nonnull # noreturn # nothrow # optimize # pure # unused # used # visibility # warning # warn_unused_result # weak # weakref # # Unsuppored function attributes will be tested with a prototype returning # an int and not accepting any arguments and the result of the check might # be wrong or meaningless so use with care. # # LICENSE # # Copyright (c) 2013 Gabriele Svelto # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [ AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1]) AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([ m4_case([$1], [alias], [ int foo( void ) { return 0; } int bar( void ) __attribute__(($1("foo"))); ], [aligned], [ int foo( void ) __attribute__(($1(32))); ], [alloc_size], [ void *foo(int a) __attribute__(($1(1))); ], [always_inline], [ inline __attribute__(($1)) int foo( void ) { return 0; } ], [artificial], [ inline __attribute__(($1)) int foo( void ) { return 0; } ], [cold], [ int foo( void ) __attribute__(($1)); ], [const], [ int foo( void ) __attribute__(($1)); ], [constructor], [ int foo( void ) __attribute__(($1)); ], [deprecated], [ int foo( void ) __attribute__(($1(""))); ], [destructor], [ int foo( void ) __attribute__(($1)); ], [dllexport], [ __attribute__(($1)) int foo( void ) { return 0; } ], [dllimport], [ int foo( void ) __attribute__(($1)); ], [error], [ int foo( void ) __attribute__(($1(""))); ], [externally_visible], [ int foo( void ) __attribute__(($1)); ], [flatten], [ int foo( void ) __attribute__(($1)); ], [format], [ int foo(const char *p, ...) __attribute__(($1(printf, 1, 2))); ], [format_arg], [ char *foo(const char *p) __attribute__(($1(1))); ], [gnu_inline], [ inline __attribute__(($1)) int foo( void ) { return 0; } ], [hot], [ int foo( void ) __attribute__(($1)); ], [ifunc], [ int my_foo( void ) { return 0; } static int (*resolve_foo(void))(void) { return my_foo; } int foo( void ) __attribute__(($1("resolve_foo"))); ], [leaf], [ __attribute__(($1)) int foo( void ) { return 0; } ], [malloc], [ void *foo( void ) __attribute__(($1)); ], [noclone], [ int foo( void ) __attribute__(($1)); ], [noinline], [ __attribute__(($1)) int foo( void ) { return 0; } ], [nonnull], [ int foo(char *p) __attribute__(($1(1))); ], [noreturn], [ void foo( void ) __attribute__(($1)); ], [nothrow], [ int foo( void ) __attribute__(($1)); ], [optimize], [ __attribute__(($1(3))) int foo( void ) { return 0; } ], [pure], [ int foo( void ) __attribute__(($1)); ], [unused], [ int foo( void ) __attribute__(($1)); ], [used], [ int foo( void ) __attribute__(($1)); ], [visibility], [ int foo_def( void ) __attribute__(($1("default"))); int foo_hid( void ) __attribute__(($1("hidden"))); int foo_int( void ) __attribute__(($1("internal"))); int foo_pro( void ) __attribute__(($1("protected"))); ], [warning], [ int foo( void ) __attribute__(($1(""))); ], [warn_unused_result], [ int foo( void ) __attribute__(($1)); ], [weak], [ int foo( void ) __attribute__(($1)); ], [weakref], [ static int foo( void ) { return 0; } static int bar( void ) __attribute__(($1("foo"))); ], [ m4_warn([syntax], [Unsupported attribute $1, the test may fail]) int foo( void ) __attribute__(($1)); ] )], []) ], dnl GCC doesn't exit with an error if an unknown attribute is dnl provided but only outputs a warning, so accept the attribute dnl only if no warning were issued. [AS_IF([test -s conftest.err], [AS_VAR_SET([ac_var], [no])], [AS_VAR_SET([ac_var], [yes])])], [AS_VAR_SET([ac_var], [no])]) ]) AS_IF([test yes = AS_VAR_GET([ac_var])], [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1, [Define to 1 if the system has the `$1' function attribute])], []) AS_VAR_POPDEF([ac_var]) ]) moc-2.6.0~svn-r3005/m4/ax_gcc_var_attribute.m40000664000076400000500000001102012404420426020060 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_gcc_var_attribute.html # =========================================================================== # # SYNOPSIS # # AX_GCC_VAR_ATTRIBUTE(ATTRIBUTE) # # DESCRIPTION # # This macro checks if the compiler supports one of GCC's variable # attributes; many other compilers also provide variable attributes with # the same syntax. Compiler warnings are used to detect supported # attributes as unsupported ones are ignored by default so quieting # warnings when using this macro will yield false positives. # # The ATTRIBUTE parameter holds the name of the attribute to be checked. # # If ATTRIBUTE is supported define HAVE_VAR_ATTRIBUTE_. # # The macro caches its result in the ax_cv_have_var_attribute_ # variable. # # The macro currently supports the following variable attributes: # # aligned # cleanup # common # nocommon # deprecated # mode # packed # tls_model # unused # used # vector_size # weak # dllimport # dllexport # # Unsuppored variable attributes will be tested against a global integer # variable and without any arguments given to the attribute itself; the # result of this check might be wrong or meaningless so use with care. # # LICENSE # # Copyright (c) 2013 Gabriele Svelto # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_GCC_VAR_ATTRIBUTE], [ AS_VAR_PUSHDEF([ac_var], [ax_cv_have_var_attribute_$1]) AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([ m4_case([$1], [aligned], [ int foo __attribute__(($1(32))); ], [cleanup], [ int bar(int *t) { return *t; }; ], [common], [ int foo __attribute__(($1)); ], [nocommon], [ int foo __attribute__(($1)); ], [deprecated], [ int foo __attribute__(($1)) = 0; ], [mode], [ long foo __attribute__(($1(word))); ], [packed], [ struct bar { int baz __attribute__(($1)); }; ], [tls_model], [ __thread int bar1 __attribute__(($1("global-dynamic"))); __thread int bar2 __attribute__(($1("local-dynamic"))); __thread int bar3 __attribute__(($1("initial-exec"))); __thread int bar4 __attribute__(($1("local-exec"))); ], [unused], [ int foo __attribute__(($1)); ], [used], [ int foo __attribute__(($1)); ], [vector_size], [ int foo __attribute__(($1(16))); ], [weak], [ int foo __attribute__(($1)); ], [dllimport], [ int foo __attribute__(($1)); ], [dllexport], [ int foo __attribute__(($1)); ], [ m4_warn([syntax], [Unsupported attribute $1, the test may fail]) int foo __attribute__(($1)); ] )], [ m4_case([$1], [cleanup], [ int foo __attribute__(($1(bar))) = 0; foo = foo + 1; ], [] )]) ], dnl GCC doesn't exit with an error if an unknown attribute is dnl provided but only outputs a warning, so accept the attribute dnl only if no warning were issued. [AS_IF([test -s conftest.err], [AS_VAR_SET([ac_var], [no])], [AS_VAR_SET([ac_var], [yes])])], [AS_VAR_SET([ac_var], [no])]) ]) AS_IF([test yes = AS_VAR_GET([ac_var])], [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_VAR_ATTRIBUTE_$1), 1, [Define to 1 if the system has the `$1' variable attribute])], []) AS_VAR_POPDEF([ac_var]) ]) moc-2.6.0~svn-r3005/m4/ax_path_bdb.m40000664000076400000500000005111112404420402016133 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_path_bdb.html # =========================================================================== # # SYNOPSIS # # AX_PATH_BDB([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # This macro finds the latest version of Berkeley DB on the system, and # ensures that the header file and library versions match. If # MINIMUM-VERSION is specified, it will ensure that the library found is # at least that version. # # It determines the name of the library as well as the path to the header # file and library. It will check both the default environment as well as # the default Berkeley DB install location. When found, it sets BDB_LIBS, # BDB_CPPFLAGS, and BDB_LDFLAGS to the necessary values to add to LIBS, # CPPFLAGS, and LDFLAGS, as well as setting BDB_VERSION to the version # found. HAVE_DB_H is defined also. # # The option --with-bdb-dir=DIR can be used to specify a specific Berkeley # DB installation to use. # # An example of it's use is: # # AX_PATH_BDB([3],[ # LIBS="$BDB_LIBS $LIBS" # LDFLAGS="$BDB_LDFLAGS $LDFLAGS" # CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" # ]) # # which will locate the latest version of Berkeley DB on the system, and # ensure that it is version 3.0 or higher. # # Details: This macro does not use either AC_CHECK_HEADERS or AC_CHECK_LIB # because, first, the functions inside the library are sometimes renamed # to contain a version code that is only available from the db.h on the # system, and second, because it is common to have multiple db.h and libdb # files on a system it is important to make sure the ones being used # correspond to the same version. Additionally, there are many different # possible names for libdb when installed by an OS distribution, and these # need to be checked if db.h does not correspond to libdb. # # When cross compiling, only header versions are verified since it would # be difficult to check the library version. Additionally the default # Berkeley DB installation locations /usr/local/BerkeleyDB* are not # searched for higher versions of the library. # # The format for the list of library names to search came from the Cyrus # IMAP distribution, although they are generated dynamically here, and # only for the version found in db.h. # # The macro AX_COMPARE_VERSION is required to use this macro, and should # be available from the Autoconf Macro Archive. # # The author would like to acknowledge the generous and valuable feedback # from Guido Draheim, without which this macro would be far less robust, # and have poor and inconsistent cross compilation support. # # Changes: # # 1/5/05 applied patch from Rafal Rzepecki to eliminate compiler # warning about unused variable, argv # # LICENSE # # Copyright (c) 2008 Tim Toolan # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 dnl ######################################################################### AC_DEFUN([AX_PATH_BDB], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_ok=no # Add --with-bdb-dir option to configure. AC_ARG_WITH([bdb-dir], [AS_HELP_STRING([--with-bdb-dir=DIR], [Berkeley DB installation directory])]) # Check if --with-bdb-dir was specified. if test "x$with_bdb_dir" = "x" ; then # No option specified, so just search the system. AX_PATH_BDB_NO_OPTIONS([$1], [HIGHEST], [ ax_path_bdb_ok=yes ]) else # Set --with-bdb-dir option. ax_path_bdb_INC="$with_bdb_dir/include" ax_path_bdb_LIB="$with_bdb_dir/lib" dnl # Save previous environment, and modify with new stuff. ax_path_bdb_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="-I$ax_path_bdb_INC $CPPFLAGS" ax_path_bdb_save_LDFLAGS=$LDFLAGS LDFLAGS="-L$ax_path_bdb_LIB $LDFLAGS" # Check for specific header file db.h AC_MSG_CHECKING([db.h presence in $ax_path_bdb_INC]) if test -f "$ax_path_bdb_INC/db.h" ; then AC_MSG_RESULT([yes]) # Check for library AX_PATH_BDB_NO_OPTIONS([$1], [ENVONLY], [ ax_path_bdb_ok=yes BDB_CPPFLAGS="-I$ax_path_bdb_INC" BDB_LDFLAGS="-L$ax_path_bdb_LIB" ]) else AC_MSG_RESULT([no]) AC_MSG_NOTICE([no usable Berkeley DB not found]) fi dnl # Restore the environment. CPPFLAGS="$ax_path_bdb_save_CPPFLAGS" LDFLAGS="$ax_path_bdb_save_LDFLAGS" fi dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_ok" = "yes" ; then m4_ifvaln([$2],[$2],[:])dnl m4_ifvaln([$3],[else $3])dnl fi ]) dnl AX_PATH_BDB dnl ######################################################################### dnl Check for berkeley DB of at least MINIMUM-VERSION on system. dnl dnl The OPTION argument determines how the checks occur, and can be one of: dnl dnl HIGHEST - Check both the environment and the default installation dnl directories for Berkeley DB and choose the version that dnl is highest. (default) dnl ENVFIRST - Check the environment first, and if no satisfactory dnl library is found there check the default installation dnl directories for Berkeley DB which is /usr/local/BerkeleyDB* dnl ENVONLY - Check the current environment only. dnl dnl Requires AX_PATH_BDB_PATH_GET_VERSION, AX_PATH_BDB_PATH_FIND_HIGHEST, dnl AX_PATH_BDB_ENV_CONFIRM_LIB, AX_PATH_BDB_ENV_GET_VERSION, and dnl AX_COMPARE_VERSION macros. dnl dnl Result: sets ax_path_bdb_no_options_ok to yes or no dnl sets BDB_LIBS, BDB_CPPFLAGS, BDB_LDFLAGS, BDB_VERSION dnl dnl AX_PATH_BDB_NO_OPTIONS([MINIMUM-VERSION], [OPTION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_NO_OPTIONS], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_no_options_ok=no # Values to add to environment to use Berkeley DB. BDB_VERSION='' BDB_LIBS='' BDB_CPPFLAGS='' BDB_LDFLAGS='' # Check cross compilation here. if test "x$cross_compiling" = "xyes" ; then # If cross compiling, can't use AC_RUN_IFELSE so do these tests. # The AC_PREPROC_IFELSE confirms that db.h is preprocessable, # and extracts the version number from it. AC_MSG_CHECKING([for db.h]) AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_no_options_HEADER_VERSION])dnl HEADER_VERSION='' AC_PREPROC_IFELSE([ AC_LANG_SOURCE([[ #include AX_PATH_BDB_STUFF DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH ]]) ],[ # Extract version from preprocessor output. HEADER_VERSION=`eval "$ac_cpp conftest.$ac_ext" 2> /dev/null \ | grep AX_PATH_BDB_STUFF | sed 's/[[^0-9,]]//g;s/,/./g;1q'` ],[]) if test "x$HEADER_VERSION" = "x" ; then AC_MSG_RESULT([no]) else AC_MSG_RESULT([$HEADER_VERSION]) # Check that version is high enough. AX_COMPARE_VERSION([$HEADER_VERSION],[ge],[$1],[ # get major and minor version numbers AS_VAR_PUSHDEF([MAJ],[ax_path_bdb_no_options_MAJOR])dnl MAJ=`echo $HEADER_VERSION | sed 's,\..*,,'` AS_VAR_PUSHDEF([MIN],[ax_path_bdb_no_options_MINOR])dnl MIN=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` dnl # Save LIBS. ax_path_bdb_no_options_save_LIBS="$LIBS" # Check that we can link with the library. AC_SEARCH_LIBS([db_version], [db db-$MAJ.$MIN db$MAJ.$MIN db$MAJ$MIN db-$MAJ db$MAJ],[ # Sucessfully found library. ax_path_bdb_no_options_ok=yes BDB_VERSION=$HEADER_VERSION # Extract library from LIBS ax_path_bdb_no_options_LEN=` \ echo "x$ax_path_bdb_no_options_save_LIBS" \ | awk '{print(length)}'` BDB_LIBS=`echo "x$LIBS " \ | sed "s/.\{$ax_path_bdb_no_options_LEN\}\$//;s/^x//;s/ //g"` ],[]) dnl # Restore LIBS LIBS="$ax_path_bdb_no_options_save_LIBS" AS_VAR_POPDEF([MAJ])dnl AS_VAR_POPDEF([MIN])dnl ]) fi AS_VAR_POPDEF([HEADER_VERSION])dnl else # Not cross compiling. # Check version of Berkeley DB in the current environment. AX_PATH_BDB_ENV_GET_VERSION([ AX_COMPARE_VERSION([$ax_path_bdb_env_get_version_VERSION],[ge],[$1],[ # Found acceptable version in current environment. ax_path_bdb_no_options_ok=yes BDB_VERSION="$ax_path_bdb_env_get_version_VERSION" BDB_LIBS="$ax_path_bdb_env_get_version_LIBS" ]) ]) # Determine if we need to search /usr/local/BerkeleyDB* ax_path_bdb_no_options_DONE=no if test "x$2" = "xENVONLY" ; then ax_path_bdb_no_options_DONE=yes elif test "x$2" = "xENVFIRST" ; then ax_path_bdb_no_options_DONE=$ax_path_bdb_no_options_ok fi if test "$ax_path_bdb_no_options_DONE" = "no" ; then # Check for highest in /usr/local/BerkeleyDB* AX_PATH_BDB_PATH_FIND_HIGHEST([ if test "$ax_path_bdb_no_options_ok" = "yes" ; then # If we already have an acceptable version use this if higher. AX_COMPARE_VERSION( [$ax_path_bdb_path_find_highest_VERSION],[gt],[$BDB_VERSION]) else # Since we didn't have an acceptable version check if this one is. AX_COMPARE_VERSION( [$ax_path_bdb_path_find_highest_VERSION],[ge],[$1]) fi ]) dnl # If result from _AX_COMPARE_VERSION is true we want this version. if test "$ax_compare_version" = "true" ; then ax_path_bdb_no_options_ok=yes BDB_LIBS="-ldb" if test "x$ax_path_bdb_path_find_highest_DIR" != x ; then BDB_CPPFLAGS="-I$ax_path_bdb_path_find_highest_DIR/include" BDB_LDFLAGS="-L$ax_path_bdb_path_find_highest_DIR/lib" fi BDB_VERSION="$ax_path_bdb_path_find_highest_VERSION" fi fi fi dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_no_options_ok" = "yes" ; then AC_MSG_NOTICE([using Berkeley DB version $BDB_VERSION]) AC_DEFINE([HAVE_DB_H],[1], [Define to 1 if you have the header file.]) m4_ifvaln([$3],[$3])dnl else AC_MSG_NOTICE([no Berkeley DB version $1 or higher found]) m4_ifvaln([$4],[$4])dnl fi ]) dnl AX_PATH_BDB_NO_OPTIONS dnl ######################################################################### dnl Check the default installation directory for Berkeley DB which is dnl of the form /usr/local/BerkeleyDB* for the highest version. dnl dnl Result: sets ax_path_bdb_path_find_highest_ok to yes or no, dnl sets ax_path_bdb_path_find_highest_VERSION to version, dnl sets ax_path_bdb_path_find_highest_DIR to directory. dnl dnl AX_PATH_BDB_PATH_FIND_HIGHEST([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_PATH_FIND_HIGHEST], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_path_find_highest_ok=no AS_VAR_PUSHDEF([VERSION],[ax_path_bdb_path_find_highest_VERSION])dnl VERSION='' ax_path_bdb_path_find_highest_DIR='' # find highest verison in default install directory for Berkeley DB AS_VAR_PUSHDEF([CURDIR],[ax_path_bdb_path_find_highest_CURDIR])dnl AS_VAR_PUSHDEF([CUR_VERSION],[ax_path_bdb_path_get_version_VERSION])dnl for CURDIR in `ls -d /usr/local/BerkeleyDB* 2> /dev/null` do AX_PATH_BDB_PATH_GET_VERSION([$CURDIR],[ AX_COMPARE_VERSION([$CUR_VERSION],[gt],[$VERSION],[ ax_path_bdb_path_find_highest_ok=yes ax_path_bdb_path_find_highest_DIR="$CURDIR" VERSION="$CUR_VERSION" ]) ]) done AS_VAR_POPDEF([VERSION])dnl AS_VAR_POPDEF([CUR_VERSION])dnl AS_VAR_POPDEF([CURDIR])dnl dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_path_find_highest_ok" = "yes" ; then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ]) dnl AX_PATH_BDB_PATH_FIND_HIGHEST dnl ######################################################################### dnl Checks for Berkeley DB in specified directory's lib and include dnl subdirectories. dnl dnl Result: sets ax_path_bdb_path_get_version_ok to yes or no, dnl sets ax_path_bdb_path_get_version_VERSION to version. dnl dnl AX_PATH_BDB_PATH_GET_VERSION(BDB-DIR, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_PATH_GET_VERSION], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_path_get_version_ok=no # Indicate status of checking for Berkeley DB header. AC_MSG_CHECKING([in $1/include for db.h]) ax_path_bdb_path_get_version_got_header=no test -f "$1/include/db.h" && ax_path_bdb_path_get_version_got_header=yes AC_MSG_RESULT([$ax_path_bdb_path_get_version_got_header]) # Indicate status of checking for Berkeley DB library. AC_MSG_CHECKING([in $1/lib for library -ldb]) ax_path_bdb_path_get_version_VERSION='' if test -d "$1/include" && test -d "$1/lib" && test "$ax_path_bdb_path_get_version_got_header" = "yes" ; then dnl # save and modify environment ax_path_bdb_path_get_version_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="-I$1/include $CPPFLAGS" ax_path_bdb_path_get_version_save_LIBS="$LIBS" LIBS="$LIBS -ldb" ax_path_bdb_path_get_version_save_LDFLAGS="$LDFLAGS" LDFLAGS="-L$1/lib $LDFLAGS" # Compile and run a program that compares the version defined in # the header file with a version defined in the library function # db_version. AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { int major,minor,patch; (void) argv; db_version(&major,&minor,&patch); if (argc > 1) printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && DB_VERSION_PATCH == patch) return 0; else return 1; } ]]) ],[ # Program compiled and ran, so get version by adding argument. ax_path_bdb_path_get_version_VERSION=`./conftest$ac_exeext x` ax_path_bdb_path_get_version_ok=yes ],[],[]) dnl # restore environment CPPFLAGS="$ax_path_bdb_path_get_version_save_CPPFLAGS" LIBS="$ax_path_bdb_path_get_version_save_LIBS" LDFLAGS="$ax_path_bdb_path_get_version_save_LDFLAGS" fi dnl # Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_path_get_version_ok" = "yes" ; then AC_MSG_RESULT([$ax_path_bdb_path_get_version_VERSION]) m4_ifvaln([$2],[$2])dnl else AC_MSG_RESULT([no]) m4_ifvaln([$3],[$3])dnl fi ]) dnl AX_PATH_BDB_PATH_GET_VERSION ############################################################################# dnl Checks if version of library and header match specified version. dnl Only meant to be used by AX_PATH_BDB_ENV_GET_VERSION macro. dnl dnl Requires AX_COMPARE_VERSION macro. dnl dnl Result: sets ax_path_bdb_env_confirm_lib_ok to yes or no. dnl dnl AX_PATH_BDB_ENV_CONFIRM_LIB(VERSION, [LIBNAME]) AC_DEFUN([AX_PATH_BDB_ENV_CONFIRM_LIB], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_env_confirm_lib_ok=no dnl # save and modify environment to link with library LIBNAME ax_path_bdb_env_confirm_lib_save_LIBS="$LIBS" LIBS="$LIBS $2" # Compile and run a program that compares the version defined in # the header file with a version defined in the library function # db_version. AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { int major,minor,patch; (void) argv; db_version(&major,&minor,&patch); if (argc > 1) printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && DB_VERSION_PATCH == patch) return 0; else return 1; } ]]) ],[ # Program compiled and ran, so get version by giving an argument, # which will tell the program to print the output. ax_path_bdb_env_confirm_lib_VERSION=`./conftest$ac_exeext x` # If the versions all match up, indicate success. AX_COMPARE_VERSION([$ax_path_bdb_env_confirm_lib_VERSION],[eq],[$1],[ ax_path_bdb_env_confirm_lib_ok=yes ]) ],[],[]) dnl # restore environment LIBS="$ax_path_bdb_env_confirm_lib_save_LIBS" ]) dnl AX_PATH_BDB_ENV_CONFIRM_LIB ############################################################################# dnl Finds the version and library name for Berkeley DB in the dnl current environment. Tries many different names for library. dnl dnl Requires AX_PATH_BDB_ENV_CONFIRM_LIB macro. dnl dnl Result: set ax_path_bdb_env_get_version_ok to yes or no, dnl set ax_path_bdb_env_get_version_VERSION to the version found, dnl and ax_path_bdb_env_get_version_LIBNAME to the library name. dnl dnl AX_PATH_BDB_ENV_GET_VERSION([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_ENV_GET_VERSION], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_env_get_version_ok=no ax_path_bdb_env_get_version_VERSION='' ax_path_bdb_env_get_version_LIBS='' AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_env_get_version_HEADER_VERSION])dnl AS_VAR_PUSHDEF([TEST_LIBNAME],[ax_path_bdb_env_get_version_TEST_LIBNAME])dnl # Indicate status of checking for Berkeley DB library. AC_MSG_CHECKING([for db.h]) # Compile and run a program that determines the Berkeley DB version # in the header file db.h. HEADER_VERSION='' AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { (void) argv; if (argc > 1) printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); return 0; } ]]) ],[ # Program compiled and ran, so get version by adding an argument. HEADER_VERSION=`./conftest$ac_exeext x` AC_MSG_RESULT([$HEADER_VERSION]) ],[AC_MSG_RESULT([no])],[AC_MSG_RESULT([no])]) # Have header version, so try to find corresponding library. # Looks for library names in the order: # nothing, db, db-X.Y, dbX.Y, dbXY, db-X, dbX # and stops when it finds the first one that matches the version # of the header file. if test "x$HEADER_VERSION" != "x" ; then AC_MSG_CHECKING([for library containing Berkeley DB $HEADER_VERSION]) AS_VAR_PUSHDEF([MAJOR],[ax_path_bdb_env_get_version_MAJOR])dnl AS_VAR_PUSHDEF([MINOR],[ax_path_bdb_env_get_version_MINOR])dnl # get major and minor version numbers MAJOR=`echo $HEADER_VERSION | sed 's,\..*,,'` MINOR=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` # see if it is already specified in LIBS TEST_LIBNAME='' AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db" TEST_LIBNAME='-ldb' AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db-X.Y" TEST_LIBNAME="-ldb-${MAJOR}.$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbX.Y" TEST_LIBNAME="-ldb${MAJOR}.$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbXY" TEST_LIBNAME="-ldb$MAJOR$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db-X" TEST_LIBNAME="-ldb-$MAJOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbX" TEST_LIBNAME="-ldb$MAJOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi dnl # Found a valid library. if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then if test "x$TEST_LIBNAME" = "x" ; then AC_MSG_RESULT([none required]) else AC_MSG_RESULT([$TEST_LIBNAME]) fi ax_path_bdb_env_get_version_VERSION="$HEADER_VERSION" ax_path_bdb_env_get_version_LIBS="$TEST_LIBNAME" ax_path_bdb_env_get_version_ok=yes else AC_MSG_RESULT([no]) fi AS_VAR_POPDEF([MAJOR])dnl AS_VAR_POPDEF([MINOR])dnl fi AS_VAR_POPDEF([HEADER_VERSION])dnl AS_VAR_POPDEF([TEST_LIBNAME])dnl dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ]) dnl BDB_ENV_GET_VERSION ############################################################################# moc-2.6.0~svn-r3005/m4/ax_pthread.m40000664000076400000500000003267612404420402016036 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also link it with them as well. e.g. you should link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threads programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # # 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 3 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 21 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case ${host_os} in solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; darwin*) ax_pthread_flags="-pthread $ax_pthread_flags" ;; esac # Clang doesn't consider unrecognized options an error unless we specify # -Werror. We throw in some extra Clang-specific options to ensure that # this doesn't happen for GCC, which also accepts -Werror. AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) save_CFLAGS="$CFLAGS" ax_pthread_extra_flags="-Werror" CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], [AC_MSG_RESULT([yes])], [ax_pthread_extra_flags= AC_MSG_RESULT([no])]) CFLAGS="$save_CFLAGS" if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $flag]) PTHREAD_CFLAGS="$flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$flag]) PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include static void routine(void *a) { a = 0; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_MSG_CHECKING([for joinable pthread attribute]) attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $attr; return attr /* ; */])], [attr_name=$attr; break], []) done AC_MSG_RESULT([$attr_name]) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi AC_MSG_CHECKING([if more special flags are required for pthreads]) flag=no case ${host_os} in aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; osf* | hpux*) flag="-D_REENTRANT";; solaris*) if test "$GCC" = "yes"; then flag="-D_REENTRANT" else # TODO: What about Clang on Solaris? flag="-mt -D_REENTRANT" fi ;; esac AC_MSG_RESULT([$flag]) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: compile with *_r variant if test "x$GCC" != xyes; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD moc-2.6.0~svn-r3005/m4/ax_require_defined.m40000664000076400000500000000230112754221520017527 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 1 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED moc-2.6.0~svn-r3005/m4/ax_with_curses.m40000664000076400000500000006642312754221520016753 0ustar riesebiesrc# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_with_curses.html # =========================================================================== # # SYNOPSIS # # AX_WITH_CURSES # # DESCRIPTION # # This macro checks whether a SysV or X/Open-compatible Curses library is # present, along with the associated header file. The NcursesW # (wide-character) library is searched for first, followed by Ncurses, # then the system-default plain Curses. The first library found is the # one returned. Finding libraries will first be attempted by using # pkg-config, and should the pkg-config files not be available, will # fallback to combinations of known flags itself. # # The following options are understood: --with-ncursesw, --with-ncurses, # --without-ncursesw, --without-ncurses. The "--with" options force the # macro to use that particular library, terminating with an error if not # found. The "--without" options simply skip the check for that library. # The effect on the search pattern is: # # (no options) - NcursesW, Ncurses, Curses # --with-ncurses --with-ncursesw - NcursesW only [*] # --without-ncurses --with-ncursesw - NcursesW only [*] # --with-ncursesw - NcursesW only [*] # --with-ncurses --without-ncursesw - Ncurses only [*] # --with-ncurses - NcursesW, Ncurses [**] # --without-ncurses --without-ncursesw - Curses only # --without-ncursesw - Ncurses, Curses # --without-ncurses - NcursesW, Curses # # [*] If the library is not found, abort the configure script. # # [**] If the second library (Ncurses) is not found, abort configure. # # The following preprocessor symbols may be defined by this macro if the # appropriate conditions are met: # # HAVE_CURSES - if any SysV or X/Open Curses library found # HAVE_CURSES_ENHANCED - if library supports X/Open Enhanced functions # HAVE_CURSES_COLOR - if library supports color (enhanced functions) # HAVE_CURSES_OBSOLETE - if library supports certain obsolete features # HAVE_NCURSESW - if NcursesW (wide char) library is to be used # HAVE_NCURSES - if the Ncurses library is to be used # # HAVE_CURSES_H - if is present and should be used # HAVE_NCURSESW_H - if should be used # HAVE_NCURSES_H - if should be used # HAVE_NCURSESW_CURSES_H - if should be used # HAVE_NCURSES_CURSES_H - if should be used # # (These preprocessor symbols are discussed later in this document.) # # The following output variables are defined by this macro; they are # precious and may be overridden on the ./configure command line: # # CURSES_LIBS - library to add to xxx_LDADD # CURSES_CFLAGS - include paths to add to xxx_CPPFLAGS # # In previous versions of this macro, the flags CURSES_LIB and # CURSES_CPPFLAGS were defined. These have been renamed, in keeping with # AX_WITH_CURSES's close bigger brother, PKG_CHECK_MODULES, which should # eventually supersede the use of AX_WITH_CURSES. Neither the library # listed in CURSES_LIBS, nor the flags in CURSES_CFLAGS are added to LIBS, # respectively CPPFLAGS, by default. You need to add both to the # appropriate xxx_LDADD/xxx_CPPFLAGS line in your Makefile.am. For # example: # # prog_LDADD = @CURSES_LIBS@ # prog_CPPFLAGS = @CURSES_CFLAGS@ # # If CURSES_LIBS is set on the configure command line (such as by running # "./configure CURSES_LIBS=-lmycurses"), then the only header searched for # is . If the user needs to specify an alternative path for a # library (such as for a non-standard NcurseW), the user should use the # LDFLAGS variable. # # The following shell variables may be defined by this macro: # # ax_cv_curses - set to "yes" if any Curses library found # ax_cv_curses_enhanced - set to "yes" if Enhanced functions present # ax_cv_curses_color - set to "yes" if color functions present # ax_cv_curses_obsolete - set to "yes" if obsolete features present # # ax_cv_ncursesw - set to "yes" if NcursesW library found # ax_cv_ncurses - set to "yes" if Ncurses library found # ax_cv_plaincurses - set to "yes" if plain Curses library found # ax_cv_curses_which - set to "ncursesw", "ncurses", "plaincurses" or "no" # # These variables can be used in your configure.ac to determine the level # of support you need from the Curses library. For example, if you must # have either Ncurses or NcursesW, you could include: # # AX_WITH_CURSES # if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then # AC_MSG_ERROR([requires either NcursesW or Ncurses library]) # fi # # If any Curses library will do (but one must be present and must support # color), you could use: # # AX_WITH_CURSES # if test "x$ax_cv_curses" != xyes || test "x$ax_cv_curses_color" != xyes; then # AC_MSG_ERROR([requires an X/Open-compatible Curses library with color]) # fi # # Certain preprocessor symbols and shell variables defined by this macro # can be used to determine various features of the Curses library. In # particular, HAVE_CURSES and ax_cv_curses are defined if the Curses # library found conforms to the traditional SysV and/or X/Open Base Curses # definition. Any working Curses library conforms to this level. # # HAVE_CURSES_ENHANCED and ax_cv_curses_enhanced are defined if the # library supports the X/Open Enhanced Curses definition. In particular, # the wide-character types attr_t, cchar_t and wint_t, the functions # wattr_set() and wget_wch() and the macros WA_NORMAL and _XOPEN_CURSES # are checked. The Ncurses library does NOT conform to this definition, # although NcursesW does. # # HAVE_CURSES_COLOR and ax_cv_curses_color are defined if the library # supports color functions and macros such as COLOR_PAIR, A_COLOR, # COLOR_WHITE, COLOR_RED and init_pair(). These are NOT part of the # X/Open Base Curses definition, but are part of the Enhanced set of # functions. The Ncurses library DOES support these functions, as does # NcursesW. # # HAVE_CURSES_OBSOLETE and ax_cv_curses_obsolete are defined if the # library supports certain features present in SysV and BSD Curses but not # defined in the X/Open definition. In particular, the functions # getattrs(), getcurx() and getmaxx() are checked. # # To use the HAVE_xxx_H preprocessor symbols, insert the following into # your system.h (or equivalent) header file: # # #if defined HAVE_NCURSESW_CURSES_H # # include # #elif defined HAVE_NCURSESW_H # # include # #elif defined HAVE_NCURSES_CURSES_H # # include # #elif defined HAVE_NCURSES_H # # include # #elif defined HAVE_CURSES_H # # include # #else # # error "SysV or X/Open-compatible Curses header file required" # #endif # # For previous users of this macro: you should not need to change anything # in your configure.ac or Makefile.am, as the previous (serial 10) # semantics are still valid. However, you should update your system.h (or # equivalent) header file to the fragment shown above. You are encouraged # also to make use of the extended functionality provided by this version # of AX_WITH_CURSES, as well as in the additional macros # AX_WITH_CURSES_PANEL, AX_WITH_CURSES_MENU and AX_WITH_CURSES_FORM. # # LICENSE # # Copyright (c) 2009 Mark Pulford # Copyright (c) 2009 Damian Pietras # Copyright (c) 2012 Reuben Thomas # Copyright (c) 2011 John Zaitseff # # 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 3 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 17 # internal function to factorize common code that is used by both ncurses # and ncursesw AC_DEFUN([_FIND_CURSES_FLAGS], [ AC_MSG_CHECKING([for $1 via pkg-config]) AX_REQUIRE_DEFINED([PKG_CHECK_EXISTS]) _PKG_CONFIG([_ax_cv_$1_libs], [libs], [$1]) _PKG_CONFIG([_ax_cv_$1_cppflags], [cflags], [$1]) AS_IF([test "x$pkg_failed" = "xyes" || test "x$pkg_failed" = "xuntried"],[ AC_MSG_RESULT([no]) # No suitable .pc file found, have to find flags via fallback AC_CACHE_CHECK([for $1 via fallback], [ax_cv_$1], [ AS_ECHO() pkg_cv__ax_cv_$1_libs="-l$1" pkg_cv__ax_cv_$1_cppflags="-D_GNU_SOURCE $CURSES_CFLAGS" LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" CPPFLAGS="$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags" AC_MSG_CHECKING([for initscr() with $pkg_cv__ax_cv_$1_libs]) AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])], [ AC_MSG_RESULT([yes]) AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs]) AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[ ax_cv_$1=yes ],[ AC_MSG_RESULT([no]) m4_if( [$1],[ncursesw],[pkg_cv__ax_cv_$1_libs="$pkg_cv__ax_cv_$1_libs -ltinfow"], [$1],[ncurses],[pkg_cv__ax_cv_$1_libs="$pkg_cv__ax_cv_$1_libs -ltinfo"] ) LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs]) AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[ ax_cv_$1=yes ],[ ax_cv_$1=no ]) ]) ],[ ax_cv_$1=no ]) ]) ],[ AC_MSG_RESULT([yes]) # Found .pc file, using its information LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" CPPFLAGS="$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags" ax_cv_$1=yes ]) ]) AU_ALIAS([MP_WITH_CURSES], [AX_WITH_CURSES]) AC_DEFUN([AX_WITH_CURSES], [ AC_ARG_VAR([CURSES_LIBS], [linker library for Curses, e.g. -lcurses]) AC_ARG_VAR([CURSES_CFLAGS], [preprocessor flags for Curses, e.g. -I/usr/include/ncursesw]) AC_ARG_WITH([ncurses], [AS_HELP_STRING([--with-ncurses], [force the use of Ncurses or NcursesW])], [], [with_ncurses=check]) AC_ARG_WITH([ncursesw], [AS_HELP_STRING([--without-ncursesw], [do not use NcursesW (wide character support)])], [], [with_ncursesw=check]) ax_saved_LIBS=$LIBS ax_saved_CPPFLAGS=$CPPFLAGS AS_IF([test "x$with_ncurses" = xyes || test "x$with_ncursesw" = xyes], [ax_with_plaincurses=no], [ax_with_plaincurses=check]) ax_cv_curses_which=no # Test for NcursesW AS_IF([test "x$CURSES_LIBS" = x && test "x$with_ncursesw" != xno], [ _FIND_CURSES_FLAGS([ncursesw]) AS_IF([test "x$ax_cv_ncursesw" = xno && test "x$with_ncursesw" = xyes], [ AC_MSG_ERROR([--with-ncursesw specified but could not find NcursesW library]) ]) AS_IF([test "x$ax_cv_ncursesw" = xyes], [ ax_cv_curses=yes ax_cv_curses_which=ncursesw CURSES_LIBS="$pkg_cv__ax_cv_ncursesw_libs" CURSES_CFLAGS="$pkg_cv__ax_cv_ncursesw_cppflags" AC_DEFINE([HAVE_NCURSESW], [1], [Define to 1 if the NcursesW library is present]) AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) AC_CACHE_CHECK([for working ncursesw/curses.h], [ax_cv_header_ncursesw_curses_h], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@define _XOPEN_SOURCE_EXTENDED 1 @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; attr_t d = WA_NORMAL; cchar_t e; wint_t f; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); wattr_set(stdscr, d, 0, NULL); wget_wch(stdscr, &f); ]])], [ax_cv_header_ncursesw_curses_h=yes], [ax_cv_header_ncursesw_curses_h=no]) ]) AS_IF([test "x$ax_cv_header_ncursesw_curses_h" = xyes], [ ax_cv_curses_enhanced=yes ax_cv_curses_color=yes ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) AC_DEFINE([HAVE_NCURSESW_CURSES_H], [1], [Define to 1 if is present]) ]) AC_CACHE_CHECK([for working ncursesw.h], [ax_cv_header_ncursesw_h], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@define _XOPEN_SOURCE_EXTENDED 1 @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; attr_t d = WA_NORMAL; cchar_t e; wint_t f; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); wattr_set(stdscr, d, 0, NULL); wget_wch(stdscr, &f); ]])], [ax_cv_header_ncursesw_h=yes], [ax_cv_header_ncursesw_h=no]) ]) AS_IF([test "x$ax_cv_header_ncursesw_h" = xyes], [ ax_cv_curses_enhanced=yes ax_cv_curses_color=yes ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) AC_DEFINE([HAVE_NCURSESW_H], [1], [Define to 1 if is present]) ]) AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h_with_ncursesw], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@define _XOPEN_SOURCE_EXTENDED 1 @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; attr_t d = WA_NORMAL; cchar_t e; wint_t f; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); wattr_set(stdscr, d, 0, NULL); wget_wch(stdscr, &f); ]])], [ax_cv_header_ncurses_h_with_ncursesw=yes], [ax_cv_header_ncurses_h_with_ncursesw=no]) ]) AS_IF([test "x$ax_cv_header_ncurses_h_with_ncursesw" = xyes], [ ax_cv_curses_enhanced=yes ax_cv_curses_color=yes ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) AC_DEFINE([HAVE_NCURSES_H], [1], [Define to 1 if is present]) ]) AS_IF([test "x$ax_cv_header_ncursesw_curses_h" = xno && test "x$ax_cv_header_ncursesw_h" = xno && test "x$ax_cv_header_ncurses_h_with_ncursesw" = xno], [ AC_MSG_WARN([could not find a working ncursesw/curses.h, ncursesw.h or ncurses.h]) ]) ]) ]) unset pkg_cv__ax_cv_ncursesw_libs unset pkg_cv__ax_cv_ncursesw_cppflags # Test for Ncurses AS_IF([test "x$CURSES_LIBS" = x && test "x$with_ncurses" != xno && test "x$ax_cv_curses_which" = xno], [ _FIND_CURSES_FLAGS([ncurses]) AS_IF([test "x$ax_cv_ncurses" = xno && test "x$with_ncurses" = xyes], [ AC_MSG_ERROR([--with-ncurses specified but could not find Ncurses library]) ]) AS_IF([test "x$ax_cv_ncurses" = xyes], [ ax_cv_curses=yes ax_cv_curses_which=ncurses CURSES_LIBS="$pkg_cv__ax_cv_ncurses_libs" CURSES_CFLAGS="$pkg_cv__ax_cv_ncurses_cppflags" AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the Ncurses library is present]) AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) AC_CACHE_CHECK([for working ncurses/curses.h], [ax_cv_header_ncurses_curses_h], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); ]])], [ax_cv_header_ncurses_curses_h=yes], [ax_cv_header_ncurses_curses_h=no]) ]) AS_IF([test "x$ax_cv_header_ncurses_curses_h" = xyes], [ ax_cv_curses_color=yes ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) AC_DEFINE([HAVE_NCURSES_CURSES_H], [1], [Define to 1 if is present]) ]) AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); ]])], [ax_cv_header_ncurses_h=yes], [ax_cv_header_ncurses_h=no]) ]) AS_IF([test "x$ax_cv_header_ncurses_h" = xyes], [ ax_cv_curses_color=yes ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) AC_DEFINE([HAVE_NCURSES_H], [1], [Define to 1 if is present]) ]) AS_IF([test "x$ax_cv_header_ncurses_curses_h" = xno && test "x$ax_cv_header_ncurses_h" = xno], [ AC_MSG_WARN([could not find a working ncurses/curses.h or ncurses.h]) ]) ]) ]) unset pkg_cv__ax_cv_ncurses_libs unset pkg_cv__ax_cv_ncurses_cppflags # Test for plain Curses (or if CURSES_LIBS was set by user) AS_IF([test "x$with_plaincurses" != xno && test "x$ax_cv_curses_which" = xno], [ AS_IF([test "x$CURSES_LIBS" != x], [ LIBS="$ax_saved_LIBS $CURSES_LIBS" ], [ LIBS="$ax_saved_LIBS -lcurses" ]) AC_CACHE_CHECK([for Curses library], [ax_cv_plaincurses], [ AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])], [ax_cv_plaincurses=yes], [ax_cv_plaincurses=no]) ]) AS_IF([test "x$ax_cv_plaincurses" = xyes], [ ax_cv_curses=yes ax_cv_curses_which=plaincurses AS_IF([test "x$CURSES_LIBS" = x], [ CURSES_LIBS="-lcurses" ]) AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) # Check for base conformance (and header file) AC_CACHE_CHECK([for working curses.h], [ax_cv_header_curses_h], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; initscr(); ]])], [ax_cv_header_curses_h=yes], [ax_cv_header_curses_h=no]) ]) AS_IF([test "x$ax_cv_header_curses_h" = xyes], [ AC_DEFINE([HAVE_CURSES_H], [1], [Define to 1 if is present]) # Check for X/Open Enhanced conformance AC_CACHE_CHECK([for X/Open Enhanced Curses conformance], [ax_cv_plaincurses_enhanced], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@define _XOPEN_SOURCE_EXTENDED 1 @%:@include @%:@ifndef _XOPEN_CURSES @%:@error "this Curses library is not enhanced" "this Curses library is not enhanced" @%:@endif ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; attr_t d = WA_NORMAL; cchar_t e; wint_t f; initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); wattr_set(stdscr, d, 0, NULL); wget_wch(stdscr, &f); ]])], [ax_cv_plaincurses_enhanced=yes], [ax_cv_plaincurses_enhanced=no]) ]) AS_IF([test "x$ax_cv_plaincurses_enhanced" = xyes], [ ax_cv_curses_enhanced=yes ax_cv_curses_color=yes AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) ]) # Check for color functions AC_CACHE_CHECK([for Curses color functions], [ax_cv_plaincurses_color], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@define _XOPEN_SOURCE_EXTENDED 1 @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; chtype c = COLOR_PAIR(1) & A_COLOR; initscr(); init_pair(1, COLOR_WHITE, COLOR_RED); ]])], [ax_cv_plaincurses_color=yes], [ax_cv_plaincurses_color=no]) ]) AS_IF([test "x$ax_cv_plaincurses_color" = xyes], [ ax_cv_curses_color=yes AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) ]) # Check for obsolete functions AC_CACHE_CHECK([for obsolete Curses functions], [ax_cv_plaincurses_obsolete], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ chtype a = A_BOLD; int b = KEY_LEFT; int g = getattrs(stdscr); int h = getcurx(stdscr) + getmaxx(stdscr); initscr(); ]])], [ax_cv_plaincurses_obsolete=yes], [ax_cv_plaincurses_obsolete=no]) ]) AS_IF([test "x$ax_cv_plaincurses_obsolete" = xyes], [ ax_cv_curses_obsolete=yes AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) ]) ]) AS_IF([test "x$ax_cv_header_curses_h" = xno], [ AC_MSG_WARN([could not find a working curses.h]) ]) ]) ]) AS_IF([test "x$ax_cv_curses" != xyes], [ax_cv_curses=no]) AS_IF([test "x$ax_cv_curses_enhanced" != xyes], [ax_cv_curses_enhanced=no]) AS_IF([test "x$ax_cv_curses_color" != xyes], [ax_cv_curses_color=no]) AS_IF([test "x$ax_cv_curses_obsolete" != xyes], [ax_cv_curses_obsolete=no]) LIBS=$ax_saved_LIBS CPPFLAGS=$ax_saved_CPPFLAGS unset ax_saved_LIBS unset ax_saved_CPPFLAGS ])dnl moc-2.6.0~svn-r3005/main.c0000664000076400000500000007636013327162643014242 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004-2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "server.h" #include "interface.h" #include "options.h" #include "protocol.h" #include "log.h" #include "decoder.h" #include "lists.h" #include "files.h" #include "rcc.h" static int mocp_argc; static const char **mocp_argv; static int popt_next_val = 1; static char *render_popt_command_line (); /* List of MOC-specific environment variables. */ static struct { const char *name; const char *desc; } environment_variables[] = { {"MOCP_OPTS", "Additional command line options"}, {"MOCP_POPTRC", "List of POPT configuration files"} }; struct parameters { char *config_file; int no_config_file; int debug; int only_server; int foreground; int append; int enqueue; int clear; int play; int allow_iface; int stop; int exit; int pause; int unpause; int next; int previous; int get_file_info; int toggle_pause; int playit; int seek_by; char jump_type; int jump_to; char *formatted_info_param; int get_formatted_info; char *adj_volume; char *toggle; char *on; char *off; }; /* Connect to the server, return fd of the socket or -1 on error. */ static int server_connect () { struct sockaddr_un sock_name; int sock; /* Create a socket. * For reasons why AF_UNIX is the correct constant to use in both * cases, see the commentary the SVN log for commit r2833. */ if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) return -1; sock_name.sun_family = AF_UNIX; strcpy (sock_name.sun_path, socket_name()); if (connect(sock, (struct sockaddr *)&sock_name, SUN_LEN(&sock_name)) == -1) { close (sock); return -1; } return sock; } /* Ping the server. * Return 1 if the server responds with EV_PONG, otherwise 0. */ static int ping_server (int sock) { int event; send_int(sock, CMD_PING); /* ignore errors - the server could have already closed the connection and sent EV_BUSY */ if (!get_int(sock, &event)) fatal ("Error when receiving pong response!"); return event == EV_PONG ? 1 : 0; } /* Check if a directory ./.moc exists and create if needed. */ static void check_moc_dir () { char *dir_name = create_file_name (""); struct stat file_stat; /* strip trailing slash */ dir_name[strlen(dir_name)-1] = 0; if (stat (dir_name, &file_stat) == -1) { if (errno != ENOENT) fatal ("Error trying to check for "CONFIG_DIR" directory: %s", xstrerror (errno)); if (mkdir (dir_name, 0700) == -1) fatal ("Can't create directory %s: %s", dir_name, xstrerror (errno)); } else { if (!S_ISDIR(file_stat.st_mode) || access (dir_name, W_OK)) fatal ("%s is not a writable directory!", dir_name); } } /* Run client and the server if needed. */ static void start_moc (const struct parameters *params, lists_t_strs *args) { int server_sock; if (params->foreground) { set_me_server (); server_init (params->debug, params->foreground); server_loop (); return; } server_sock = server_connect (); if (server_sock != -1 && params->only_server) fatal ("Server is already running!"); if (server_sock == -1) { int i = 0; int notify_pipe[2]; ssize_t rc; printf ("Running the server...\n"); /* To notify the client that the server socket is ready */ if (pipe(notify_pipe)) fatal ("pipe() failed: %s", xstrerror (errno)); switch (fork()) { case 0: /* child - start server */ set_me_server (); server_init (params->debug, params->foreground); rc = write (notify_pipe[1], &i, sizeof(i)); if (rc < 0) fatal ("write() to notify pipe failed: %s", xstrerror (errno)); close (notify_pipe[0]); close (notify_pipe[1]); server_loop (); options_free (); decoder_cleanup (); io_cleanup (); files_cleanup (); rcc_cleanup (); common_cleanup (); exit (EXIT_SUCCESS); case -1: fatal ("fork() failed: %s", xstrerror (errno)); default: close (notify_pipe[1]); if (read(notify_pipe[0], &i, sizeof(i)) != sizeof(i)) fatal ("Server exited!"); close (notify_pipe[0]); server_sock = server_connect (); if (server_sock == -1) { perror ("server_connect()"); fatal ("Can't connect to the server!"); } } } if (params->only_server) send_int (server_sock, CMD_DISCONNECT); else { xsignal (SIGPIPE, SIG_IGN); if (!ping_server (server_sock)) fatal ("Can't connect to the server!"); init_interface (server_sock, params->debug, args); interface_loop (); interface_end (); } close (server_sock); } /* Send commands requested in params to the server. */ static void server_command (struct parameters *params, lists_t_strs *args) { int sock; if ((sock = server_connect()) == -1) fatal ("The server is not running!"); xsignal (SIGPIPE, SIG_IGN); if (!ping_server (sock)) fatal ("Can't connect to the server!"); if (params->playit) interface_cmdline_playit (sock, args); if (params->clear) interface_cmdline_clear_plist (sock); if (params->append) interface_cmdline_append (sock, args); if (params->enqueue) interface_cmdline_enqueue (sock, args); if (params->play) interface_cmdline_play_first (sock); if (params->get_file_info) interface_cmdline_file_info (sock); if (params->seek_by) interface_cmdline_seek_by (sock, params->seek_by); if (params->jump_type=='%') interface_cmdline_jump_to_percent (sock,params->jump_to); if (params->jump_type=='s') interface_cmdline_jump_to (sock,params->jump_to); if (params->get_formatted_info) interface_cmdline_formatted_info (sock, params->formatted_info_param); if (params->adj_volume) interface_cmdline_adj_volume (sock, params->adj_volume); if (params->toggle) interface_cmdline_set (sock, params->toggle, 2); if (params->on) interface_cmdline_set (sock, params->on, 1); if (params->off) interface_cmdline_set (sock, params->off, 0); if (params->exit) { if (!send_int(sock, CMD_QUIT)) fatal ("Can't send command!"); } else if (params->stop) { if (!send_int(sock, CMD_STOP) || !send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } else if (params->pause) { if (!send_int(sock, CMD_PAUSE) || !send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } else if (params->next) { if (!send_int(sock, CMD_NEXT) || !send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } else if (params->previous) { if (!send_int(sock, CMD_PREV) || !send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } else if (params->unpause) { if (!send_int(sock, CMD_UNPAUSE) || !send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } else if (params->toggle_pause) { int state, ev, cmd = -1; if (!send_int(sock, CMD_GET_STATE)) fatal ("Can't send commands!"); if (!get_int(sock, &ev) || ev != EV_DATA || !get_int(sock, &state)) fatal ("Can't get data from the server!"); if (state == STATE_PAUSE) cmd = CMD_UNPAUSE; else if (state == STATE_PLAY) cmd = CMD_PAUSE; if (cmd != -1 && !send_int(sock, cmd)) fatal ("Can't send commands!"); if (!send_int(sock, CMD_DISCONNECT)) fatal ("Can't send commands!"); } close (sock); } static void show_version () { int rc; struct utsname uts; putchar ('\n'); printf (" This is : %s\n", PACKAGE_NAME); printf (" Version : %s\n", PACKAGE_VERSION); #ifdef PACKAGE_REVISION printf (" Revision : %s\n", PACKAGE_REVISION); #endif /* Show build time */ #ifdef __DATE__ printf (" Built : %s", __DATE__); # ifdef __TIME__ printf (" %s", __TIME__); # endif putchar ('\n'); #endif /* Show compiled-in components */ printf (" Compiled with :"); #ifdef HAVE_OSS printf (" OSS"); #endif #ifdef HAVE_SNDIO printf (" SNDIO"); #endif #ifdef HAVE_ALSA printf (" ALSA"); #endif #ifdef HAVE_JACK printf (" JACK"); #endif #ifndef NDEBUG printf (" DEBUG"); #endif #ifdef HAVE_CURL printf (" Network streams"); #endif #ifdef HAVE_SAMPLERATE printf (" resample"); #endif putchar ('\n'); rc = uname (&uts); if (rc == 0) printf (" Running on : %s %s %s\n", uts.sysname, uts.release, uts.machine); printf (" Author : Damian Pietras\n"); printf (" Homepage : %s\n", PACKAGE_URL); printf (" E-Mail : %s\n", PACKAGE_BUGREPORT); printf (" Copyright : (C) 2003-2016 Damian Pietras and others\n"); printf (" License : GNU General Public License, version 2 or later\n"); putchar ('\n'); } /* Show program banner. */ static void show_banner () { printf ("%s (version %s", PACKAGE_NAME, PACKAGE_VERSION); #ifdef PACKAGE_REVISION printf (", revision %s", PACKAGE_REVISION); #endif printf (")\n"); } static const char mocp_summary[] = "[OPTIONS] [FILE|DIR ...]"; /* Show program usage. */ static void show_usage (poptContext ctx) { show_banner (); poptSetOtherOptionHelp (ctx, mocp_summary); poptPrintUsage (ctx, stdout, 0); } /* Show program help. */ static void show_help (poptContext ctx) { size_t ix; show_banner (); poptSetOtherOptionHelp (ctx, mocp_summary); poptPrintHelp (ctx, stdout, 0); printf ("\nEnvironment variables:\n\n"); for (ix = 0; ix < ARRAY_SIZE(environment_variables); ix += 1) printf (" %-34s%s\n", environment_variables[ix].name, environment_variables[ix].desc); printf ("\n"); } /* Show POPT-interpreted command line arguments. */ static void show_args () { if (mocp_argc > 0) { char *str; str = getenv ("MOCP_POPTRC"); if (str) printf ("MOCP_POPTRC='%s' ", str); str = getenv ("MOCP_OPTS"); if (str) printf ("MOCP_OPTS='%s' ", str); str = render_popt_command_line (); printf ("%s\n", str); free (str); } } /* Disambiguate the user's request. */ static void show_misc_cb (poptContext ctx, enum poptCallbackReason unused1 ATTR_UNUSED, const struct poptOption *opt, const char *unused2 ATTR_UNUSED, void *unused3 ATTR_UNUSED) { switch (opt->shortName) { case 'V': show_version (); break; case 'h': show_help (ctx); break; case 0: if (!strcmp (opt->longName, "echo-args")) show_args (); else if (!strcmp (opt->longName, "usage")) show_usage (ctx); break; } exit (EXIT_SUCCESS); } enum { CL_HANDLED = 0, CL_NOIFACE, CL_SDRIVER, CL_MUSICDIR, CL_THEME, CL_SETOPTION, CL_MOCDIR, CL_SYNCPL, CL_NOSYNC, CL_ASCII, CL_JUMP, CL_GETINFO }; static struct parameters params; static struct poptOption general_opts[] = { #ifndef NDEBUG {"debug", 'D', POPT_ARG_NONE, ¶ms.debug, CL_HANDLED, "Turn on logging to a file", NULL}, #endif {"moc-dir", 'M', POPT_ARG_STRING, NULL, CL_MOCDIR, "Use the specified MOC directory instead of the default", "DIR"}, {"music-dir", 'm', POPT_ARG_NONE, NULL, CL_MUSICDIR, "Start in MusicDir", NULL}, {"config", 'C', POPT_ARG_STRING, ¶ms.config_file, CL_HANDLED, "Use the specified config file instead of the default" " (conflicts with '--no-config')", "FILE"}, {"no-config", 0, POPT_ARG_NONE, ¶ms.no_config_file, CL_HANDLED, "Use program defaults rather than any config file" " (conflicts with '--config')", NULL}, {"set-option", 'O', POPT_ARG_STRING, NULL, CL_SETOPTION, "Override the configuration option NAME with VALUE", "'NAME=VALUE'"}, {"foreground", 'F', POPT_ARG_NONE, ¶ms.foreground, CL_HANDLED, "Run the server in foreground (logging to stdout)", NULL}, {"server", 'S', POPT_ARG_NONE, ¶ms.only_server, CL_HANDLED, "Only run the server", NULL}, {"sound-driver", 'R', POPT_ARG_STRING, NULL, CL_SDRIVER, "Use the first valid sound driver", "DRIVERS"}, {"ascii", 'A', POPT_ARG_NONE, NULL, CL_ASCII, "Use ASCII characters to draw lines", NULL}, {"theme", 'T', POPT_ARG_STRING, NULL, CL_THEME, "Use the selected theme file (read from ~/.moc/themes if the path is not absolute)", "FILE"}, {"sync", 'y', POPT_ARG_NONE, NULL, CL_SYNCPL, "Synchronize the playlist with other clients", NULL}, {"nosync", 'n', POPT_ARG_NONE, NULL, CL_NOSYNC, "Don't synchronize the playlist with other clients", NULL}, POPT_TABLEEND }; static struct poptOption server_opts[] = { {"pause", 'P', POPT_ARG_NONE, ¶ms.pause, CL_NOIFACE, "Pause", NULL}, {"unpause", 'U', POPT_ARG_NONE, ¶ms.unpause, CL_NOIFACE, "Unpause", NULL}, {"toggle-pause", 'G', POPT_ARG_NONE, ¶ms.toggle_pause, CL_NOIFACE, "Toggle between playing and paused", NULL}, {"stop", 's', POPT_ARG_NONE, ¶ms.stop, CL_NOIFACE, "Stop playing", NULL}, {"next", 'f', POPT_ARG_NONE, ¶ms.next, CL_NOIFACE, "Play the next song", NULL}, {"previous", 'r', POPT_ARG_NONE, ¶ms.previous, CL_NOIFACE, "Play the previous song", NULL}, {"seek", 'k', POPT_ARG_INT, ¶ms.seek_by, CL_NOIFACE, "Seek by N seconds (can be negative)", "N"}, {"jump", 'j', POPT_ARG_STRING, NULL, CL_JUMP, "Jump to some position in the current track", "N{%,s}"}, {"volume", 'v', POPT_ARG_STRING, ¶ms.adj_volume, CL_NOIFACE, "Adjust the PCM volume", "[+,-]LEVEL"}, {"exit", 'x', POPT_ARG_NONE, ¶ms.exit, CL_NOIFACE, "Shutdown the server", NULL}, {"append", 'a', POPT_ARG_NONE, ¶ms.append, CL_NOIFACE, "Append the files/directories/playlists passed in " "the command line to playlist", NULL}, {"recursively", 'e', POPT_ARG_NONE, ¶ms.append, CL_NOIFACE, "Alias for --append", NULL}, {"enqueue", 'q', POPT_ARG_NONE, ¶ms.enqueue, CL_NOIFACE, "Add the files given on command line to the queue", NULL}, {"clear", 'c', POPT_ARG_NONE, ¶ms.clear, CL_NOIFACE, "Clear the playlist", NULL}, {"play", 'p', POPT_ARG_NONE, ¶ms.play, CL_NOIFACE, "Start playing from the first item on the playlist", NULL}, {"playit", 'l', POPT_ARG_NONE, ¶ms.playit, CL_NOIFACE, "Play files given on command line without modifying the playlist", NULL}, {"toggle", 't', POPT_ARG_STRING, ¶ms.toggle, CL_NOIFACE, "Toggle a control (shuffle, autonext, repeat)", "CONTROL"}, {"on", 'o', POPT_ARG_STRING, ¶ms.on, CL_NOIFACE, "Turn on a control (shuffle, autonext, repeat)", "CONTROL"}, {"off", 'u', POPT_ARG_STRING, ¶ms.off, CL_NOIFACE, "Turn off a control (shuffle, autonext, repeat)", "CONTROL"}, {"info", 'i', POPT_ARG_NONE, ¶ms.get_file_info, CL_NOIFACE, "Print information about the file currently playing", NULL}, {"format", 'Q', POPT_ARG_STRING, ¶ms.formatted_info_param, CL_GETINFO, "Print formatted information about the file currently playing", "FORMAT"}, POPT_TABLEEND }; static struct poptOption misc_opts[] = { {NULL, 0, POPT_ARG_CALLBACK, (void *) (uintptr_t) show_misc_cb, 0, NULL, NULL}, {"version", 'V', POPT_ARG_NONE, NULL, 0, "Print version information", NULL}, {"echo-args", 0, POPT_ARG_NONE, NULL, 0, "Print POPT-interpreted arguments", NULL}, {"usage", 0, POPT_ARG_NONE, NULL, 0, "Print brief usage", NULL}, {"help", 'h', POPT_ARG_NONE, NULL, 0, "Print extended usage", NULL}, POPT_TABLEEND }; static struct poptOption mocp_opts[] = { {NULL, 0, POPT_ARG_INCLUDE_TABLE, general_opts, 0, "General options:", NULL}, {NULL, 0, POPT_ARG_INCLUDE_TABLE, server_opts, 0, "Server commands:", NULL}, {NULL, 0, POPT_ARG_INCLUDE_TABLE, misc_opts, 0, "Miscellaneous options:", NULL}, POPT_AUTOALIAS POPT_TABLEEND }; /* Read the POPT configuration files as given in MOCP_POPTRC. */ static void read_mocp_poptrc (poptContext ctx, const char *env_poptrc) { int ix, rc, count; lists_t_strs *files; files = lists_strs_new (4); count = lists_strs_split (files, env_poptrc, ":"); for (ix = 0; ix < count; ix += 1) { const char *fn; fn = lists_strs_at (files, ix); if (!strlen (fn)) continue; if (!is_secure (fn)) fatal ("POPT config file is not secure: %s", fn); rc = poptReadConfigFile (ctx, fn); if (rc < 0) fatal ("Error reading POPT config file '%s': %s", fn, poptStrerror (rc)); } lists_strs_free (files); } /* Check that the ~/.popt file is secure. */ static void check_popt_secure () { int len; const char *home, dot_popt[] = ".popt"; char *home_popt; home = get_home (); len = strlen (home) + strlen (dot_popt) + 2; home_popt = xcalloc (len, sizeof (char)); snprintf (home_popt, len, "%s/%s", home, dot_popt); if (!is_secure (home_popt)) fatal ("POPT config file is not secure: %s", home_popt); free (home_popt); } /* Read the default POPT configuration file. */ static void read_default_poptrc (poptContext ctx) { int rc; check_popt_secure (); rc = poptReadDefaultConfig (ctx, 0); if (rc == POPT_ERROR_ERRNO) { int saved_errno = errno; fprintf (stderr, "\n" "WARNING: The following fatal error message may be bogus!\n" " If you have an empty /etc/popt.d directory, try\n" " adding an empty file to it. If that does not fix\n" " the problem then you have a genuine error.\n"); errno = saved_errno; } if (rc != 0) fatal ("Error reading default POPT config file: %s", poptStrerror (rc)); } /* Read the POPT configuration files(s). */ static void read_popt_config (poptContext ctx) { const char *env_poptrc; env_poptrc = getenv ("MOCP_POPTRC"); if (env_poptrc) read_mocp_poptrc (ctx, env_poptrc); else read_default_poptrc (ctx); } /* Prepend MOCP_OPTS to the command line. */ static void prepend_mocp_opts (poptContext ctx) { int rc; const char *env_opts; env_opts = getenv ("MOCP_OPTS"); if (env_opts && strlen (env_opts)) { int env_argc; const char **env_argv; rc = poptParseArgvString (env_opts, &env_argc, &env_argv); if (rc < 0) fatal ("Error parsing MOCP_OPTS: %s", poptStrerror (rc)); rc = poptStuffArgs (ctx, env_argv); if (rc < 0) fatal ("Error prepending MOCP_OPTS: %s", poptStrerror (rc)); free (env_argv); } } static const struct poptOption specials[] = {POPT_AUTOHELP POPT_AUTOALIAS POPT_TABLEEND}; /* Return true iff 'opt' is a POPT AutoHelp option. */ static inline bool is_autohelp (const struct poptOption *opt) { const struct poptOption *autohelp = &specials[0]; return opt->argInfo == autohelp->argInfo && opt->arg == autohelp->arg; } /* Return true iff 'opt' is a POPT AutoAlias option. */ static inline bool is_autoalias (const struct poptOption *opt) { const struct poptOption *autoalias = &specials[1]; return opt->argInfo == autoalias->argInfo && opt->arg == autoalias->arg; } /* Return true iff 'opt' is the POPT end-of-table marker. */ static inline bool is_tableend (const struct poptOption *opt) { const struct poptOption *tableend = &specials[2]; return opt->longName == tableend->longName && opt->shortName == tableend->shortName && opt->arg == tableend->arg; } /* Return a copy of the POPT option table structure which is suitable * for rendering the POPT expansions of the command line. */ struct poptOption *clone_popt_options (struct poptOption *opts) { size_t tally, ix, iy = 0; struct poptOption *result; assert (opts); for (tally = 1; !is_tableend (&opts[tally - 1]); tally += 1); result = xcalloc (tally, sizeof (struct poptOption)); for (ix = 0; ix < tally; ix += 1) { if (opts[ix].argInfo == POPT_ARG_CALLBACK) continue; if (is_autohelp (&opts[ix])) continue; if (is_autoalias (&opts[ix])) continue; memcpy (&result[iy], &opts[ix], sizeof (struct poptOption)); if (is_tableend (&opts[ix])) continue; if (opts[ix].argInfo == POPT_ARG_INCLUDE_TABLE) { result[iy++].arg = clone_popt_options (opts[ix].arg); continue; } switch (result[iy].argInfo) { case POPT_ARG_STRING: case POPT_ARG_INT: case POPT_ARG_LONG: case POPT_ARG_FLOAT: case POPT_ARG_DOUBLE: result[iy].argInfo = POPT_ARG_STRING; break; case POPT_ARG_VAL: result[iy].argInfo = POPT_ARG_NONE; break; case POPT_ARG_NONE: break; default: fatal ("Unknown POPT option table argInfo type: %d", result[iy].argInfo); } result[iy].arg = NULL; result[iy++].val = popt_next_val++; } return result; } /* Free a copied POPT option table structure. */ void free_popt_clone (struct poptOption *opts) { int ix; assert (opts); for (ix = 0; !is_tableend (&opts[ix]); ix += 1) { if (opts[ix].argInfo == POPT_ARG_INCLUDE_TABLE) free_popt_clone (opts[ix].arg); } free (opts); } /* Return a pointer to the copied POPT option table entry for which the * 'val' field matches 'wanted'. */ struct poptOption *find_popt_option (struct poptOption *opts, int wanted) { assert (opts); assert (LIMIT(wanted, popt_next_val)); for (size_t ix = 0; !is_tableend (&opts[ix]); ix += 1) { struct poptOption *result; assert (opts[ix].argInfo != POPT_ARG_CALLBACK); if (opts[ix].val == wanted) return &opts[ix]; switch (opts[ix].argInfo) { case POPT_ARG_INCLUDE_TABLE: result = find_popt_option (opts[ix].arg, wanted); if (result) return result; break; case POPT_ARG_STRING: case POPT_ARG_INT: case POPT_ARG_LONG: case POPT_ARG_FLOAT: case POPT_ARG_DOUBLE: case POPT_ARG_VAL: case POPT_ARG_NONE: break; default: fatal ("Unknown POPT option table argInfo type: %d", opts[ix].argInfo); } } return NULL; } /* Render the command line as interpreted by POPT. */ static char *render_popt_command_line () { int rc; lists_t_strs *cmdline; char *result; const char **rest; poptContext ctx; struct poptOption *null_opts; null_opts = clone_popt_options (mocp_opts); ctx = poptGetContext ("mocp", mocp_argc, mocp_argv, null_opts, POPT_CONTEXT_NO_EXEC); read_popt_config (ctx); prepend_mocp_opts (ctx); cmdline = lists_strs_new (mocp_argc * 2); lists_strs_append (cmdline, mocp_argv[0]); while (1) { size_t len; char *str; const char *arg; struct poptOption *opt; rc = poptGetNextOpt (ctx); if (rc == -1) break; if (rc == POPT_ERROR_BADOPT) { lists_strs_append (cmdline, poptBadOption (ctx, 0)); continue; } opt = find_popt_option (null_opts, rc); if (!opt) { result = xstrdup ("Couldn't find option in copied option table!"); goto err; } arg = poptGetOptArg (ctx); if (opt->longName) { len = strlen (opt->longName) + 3; if (arg) len += strlen (arg) + 3; str = xmalloc (len); if (arg) snprintf (str, len, "--%s='%s'", opt->longName, arg); else snprintf (str, len, "--%s", opt->longName); } else { len = 3; if (arg) len += strlen (arg) + 3; str = xmalloc (len); if (arg) snprintf (str, len, "-%c '%s'", opt->shortName, arg); else snprintf (str, len, "-%c", opt->shortName); } lists_strs_push (cmdline, str); free ((void *) arg); } rest = poptGetArgs (ctx); if (rest) lists_strs_load (cmdline, rest); result = lists_strs_fmt (cmdline, "%s "); err: poptFreeContext (ctx); free_popt_clone (null_opts); lists_strs_free (cmdline); return result; } static void override_config_option (const char *arg, lists_t_strs *deferred) { int len; bool append; char *ptr, *name, *value; enum option_type type; assert (arg != NULL); ptr = strchr (arg, '='); if (ptr == NULL) goto error; /* Allow for list append operator ("+="). */ append = (ptr > arg && *(ptr - 1) == '+'); name = trim (arg, ptr - arg - (append ? 1 : 0)); if (!name || !name[0]) goto error; type = options_get_type (name); if (type == OPTION_LIST) { if (deferred) { lists_strs_append (deferred, arg); free (name); return; } } else if (append) goto error; value = trim (ptr + 1, strlen (ptr + 1)); if (!value || !value[0]) goto error; if (value[0] == '\'' || value[0] == '"') { len = strlen (value); if (value[0] != value[len - 1]) goto error; if (strlen (value) < 2) goto error; memmove (value, value + 1, len - 2); value[len - 2] = 0x00; } if (!options_set_pair (name, value, append)) goto error; options_ignore_config (name); free (name); free (value); return; error: fatal ("Malformed override option: %s", arg); } static long get_num_param (const char *p,const char ** last) { char *e; long val; val = strtol (p, &e, 10); if ((*e&&last==NULL)||e==p) fatal ("The parameter should be a number!"); if (last) *last=e; return val; } /* Process the command line options. */ static void process_options (poptContext ctx, lists_t_strs *deferred) { int rc; while ((rc = poptGetNextOpt (ctx)) >= 0) { const char *jump_type, *arg; arg = poptGetOptArg (ctx); switch (rc) { case CL_SDRIVER: if (!options_check_list ("SoundDriver", arg)) fatal ("No such sound driver: %s", arg); options_set_list ("SoundDriver", arg, false); options_ignore_config ("SoundDriver"); break; case CL_MUSICDIR: options_set_bool ("StartInMusicDir", true); options_ignore_config ("StartInMusicDir"); break; case CL_NOIFACE: params.allow_iface = 0; break; case CL_THEME: options_set_str ("ForceTheme", arg); break; case CL_SETOPTION: override_config_option (arg, deferred); break; case CL_MOCDIR: options_set_str ("MOCDir", arg); options_ignore_config ("MOCDir"); break; case CL_SYNCPL: options_set_bool ("SyncPlaylist", true); options_ignore_config ("SyncPlaylist"); break; case CL_NOSYNC: options_set_bool ("SyncPlaylist", false); options_ignore_config ("SyncPlaylist"); break; case CL_ASCII: options_set_bool ("ASCIILines", true); options_ignore_config ("ASCIILines"); break; case CL_JUMP: params.jump_to = get_num_param (arg, &jump_type); if (*jump_type) if (!jump_type[1]) if (*jump_type == '%' || tolower (*jump_type) == 's') { params.jump_type = tolower (*jump_type); params.allow_iface = 0; break; } //TODO: Add message explaining the error show_usage (ctx); exit (EXIT_FAILURE); case CL_GETINFO: params.get_formatted_info = 1; params.allow_iface = 0; break; default: show_usage (ctx); exit (EXIT_FAILURE); } free ((void *) arg); } if (rc < -1) { const char *opt, *alias; opt = poptBadOption (ctx, 0); alias = poptBadOption (ctx, POPT_BADOPTION_NOALIAS); /* poptBadOption() with POPT_BADOPTION_NOALIAS fails to * return the correct option if poptStuffArgs() was used. */ if (!strcmp (opt, alias) || getenv ("MOCP_OPTS")) fatal ("%s: %s", opt, poptStrerror (rc)); else fatal ("%s (aliased by %s): %s", opt, alias, poptStrerror (rc)); } if (params.config_file && params.no_config_file) fatal ("Conflicting --config and --no-config options given!"); } /* Process the command line options and arguments. */ static lists_t_strs *process_command_line (lists_t_strs *deferred) { const char **rest; poptContext ctx; lists_t_strs *result; assert (deferred != NULL); ctx = poptGetContext ("mocp", mocp_argc, mocp_argv, mocp_opts, 0); read_popt_config (ctx); prepend_mocp_opts (ctx); process_options (ctx, deferred); if (params.foreground) params.only_server = 1; result = lists_strs_new (4); rest = poptGetArgs (ctx); if (rest) lists_strs_load (result, rest); poptFreeContext (ctx); return result; } static void process_deferred_overrides (lists_t_strs *deferred) { int ix; bool cleared; const char marker[] = "*Marker*"; char **config_decoders; lists_t_strs *decoders_option; /* We need to shuffle the PreferredDecoders list into the * right order as we load any deferred overriding options. */ decoders_option = options_get_list ("PreferredDecoders"); lists_strs_reverse (decoders_option); config_decoders = lists_strs_save (decoders_option); lists_strs_clear (decoders_option); lists_strs_append (decoders_option, marker); for (ix = 0; ix < lists_strs_size (deferred); ix += 1) override_config_option (lists_strs_at (deferred, ix), NULL); cleared = lists_strs_empty (decoders_option) || strcmp (lists_strs_at (decoders_option, 0), marker) != 0; lists_strs_reverse (decoders_option); if (!cleared) { char **override_decoders; free (lists_strs_pop (decoders_option)); override_decoders = lists_strs_save (decoders_option); lists_strs_clear (decoders_option); lists_strs_load (decoders_option, (const char **)config_decoders); lists_strs_load (decoders_option, (const char **)override_decoders); free (override_decoders); } free (config_decoders); } static void log_environment_variables () { #ifndef NDEBUG size_t ix; for (ix = 0; ix < ARRAY_SIZE(environment_variables); ix += 1) { char *str; str = getenv (environment_variables[ix].name); if (str) logit ("%s='%s'", environment_variables[ix].name, str); } #endif } /* Log the command line which launched MOC. */ static void log_command_line () { #ifndef NDEBUG lists_t_strs *cmdline; char *str; cmdline = lists_strs_new (mocp_argc); if (lists_strs_load (cmdline, mocp_argv) > 0) str = lists_strs_fmt (cmdline, "%s "); else str = xstrdup ("No command line available"); logit ("%s", str); free (str); lists_strs_free (cmdline); #endif } /* Log the command line as interpreted by POPT. */ static void log_popt_command_line () { #ifndef NDEBUG if (mocp_argc > 0) { char *str; str = render_popt_command_line (); logit ("%s", str); free (str); } #endif } int main (int argc, const char *argv[]) { lists_t_strs *deferred_overrides, *args; assert (argc >= 0); assert (argv != NULL); assert (argv[argc] == NULL); mocp_argc = argc; mocp_argv = argv; #ifdef PACKAGE_REVISION logit ("This is Music On Console (revision %s)", PACKAGE_REVISION); #else logit ("This is Music On Console (version %s)", PACKAGE_VERSION); #endif #ifdef CONFIGURATION logit ("Configured:%s", CONFIGURATION); #endif #if !defined(NDEBUG) { int rc; struct utsname uts; rc = uname (&uts); if (rc == 0) logit ("Running on: %s %s %s", uts.sysname, uts.release, uts.machine); } #endif files_init (); if (get_home () == NULL) fatal ("Could not determine user's home directory!"); memset (¶ms, 0, sizeof(params)); params.allow_iface = 1; options_init (); deferred_overrides = lists_strs_new (4); /* set locale according to the environment variables */ if (!setlocale(LC_ALL, "")) logit ("Could not set locale!"); log_environment_variables (); log_command_line (); args = process_command_line (deferred_overrides); log_popt_command_line (); if (!params.allow_iface && params.only_server) fatal ("Server command options can't be used with --server!"); if (!params.no_config_file) { if (params.config_file) { if (!can_read_file (params.config_file)) fatal ("Configuration file is not readable: %s", params.config_file); } else params.config_file = create_file_name ("config"); options_parse (params.config_file); } process_deferred_overrides (deferred_overrides); lists_strs_free (deferred_overrides); deferred_overrides = NULL; check_moc_dir (); io_init (); rcc_init (); decoder_init (params.debug); srand (time(NULL)); if (params.allow_iface) start_moc (¶ms, args); else server_command (¶ms, args); lists_strs_free (args); options_free (); decoder_cleanup (); io_cleanup (); rcc_cleanup (); files_cleanup (); common_cleanup (); return EXIT_SUCCESS; } moc-2.6.0~svn-r3005/md5.c0000664000076400000500000003614511742710602013771 0ustar riesebiesrc/* -*- buffer-read-only: t -*- vi: set ro: */ /* DO NOT EDIT! GENERATED AUTOMATICALLY! */ /* Functions to compute MD5 message digest of files or memory blocks. according to the definition of MD5 in RFC 1321 from April 1992. Copyright (C) 1995, 1996, 1997, 1999, 2000, 2001, 2005, 2006, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of the GNU C Library. 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 3, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Written by Ulrich Drepper , 1995. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "md5.h" #include "common.h" #include #include #include #include #if USE_UNLOCKED_IO # include "unlocked-io.h" #endif #ifdef _LIBC # include # if __BYTE_ORDER == __BIG_ENDIAN # define WORDS_BIGENDIAN 1 # endif /* We need to keep the namespace clean so define the MD5 function protected using leading __ . */ # define md5_init_ctx __md5_init_ctx # define md5_process_block __md5_process_block # define md5_process_bytes __md5_process_bytes # define md5_finish_ctx __md5_finish_ctx # define md5_read_ctx __md5_read_ctx # define md5_stream __md5_stream # define md5_buffer __md5_buffer #endif #ifdef WORDS_BIGENDIAN # define SWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) #else # define SWAP(n) (n) #endif #define BLOCKSIZE 32768 #if BLOCKSIZE % 64 != 0 # error "invalid BLOCKSIZE" #endif /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ void md5_init_ctx (struct md5_ctx *ctx) { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Copy the 4 byte value from v into the memory location pointed to by *cp, If your architecture allows unaligned access this is equivalent to * (uint32_t *) cp = v */ static inline void set_uint32 (char *cp, uint32_t v) { memcpy (cp, &v, sizeof v); } /* Put result from CTX in first 16 bytes following RESBUF. The result must be in little endian byte order. */ void * md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) { char *r = resbuf; set_uint32 (r + 0 * sizeof ctx->A, SWAP (ctx->A)); set_uint32 (r + 1 * sizeof ctx->B, SWAP (ctx->B)); set_uint32 (r + 2 * sizeof ctx->C, SWAP (ctx->C)); set_uint32 (r + 3 * sizeof ctx->D, SWAP (ctx->D)); return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. */ void * md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) { /* Take yet unprocessed bytes into account. */ uint32_t bytes = ctx->buflen; size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; /* Put the 64-bit file length in *bits* at the end of the buffer. */ ctx->buffer[size - 2] = SWAP (ctx->total[0] << 3); ctx->buffer[size - 1] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); /* Process last bytes. */ md5_process_block (ctx->buffer, size * 4, ctx); return md5_read_ctx (ctx, resbuf); } /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ int md5_stream (FILE *stream, void *resblock) { struct md5_ctx ctx; size_t sum; char *buffer = xmalloc (BLOCKSIZE + 72); if (!buffer) return 1; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ while (1) { n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; if (sum == BLOCKSIZE) break; if (n == 0) { /* Check for the error flag IFF N == 0, so that we don't exit the loop after a partial read due to e.g., EAGAIN or EWOULDBLOCK. */ if (ferror (stream)) { free (buffer); return 1; } goto process_partial_block; } /* We've read at least one byte, so ignore errors. But always check for EOF, since feof may be true even though N > 0. Otherwise, we could end up calling fread after EOF. */ if (feof (stream)) goto process_partial_block; } /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ md5_process_block (buffer, BLOCKSIZE, &ctx); } process_partial_block: /* Process any remaining bytes. */ if (sum > 0) md5_process_bytes (buffer, sum, &ctx); /* Construct result in desired memory. */ md5_finish_ctx (&ctx, resblock); free (buffer); return 0; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void * md5_buffer (const char *buffer, size_t len, void *resblock) { struct md5_ctx ctx; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Process whole buffer but last len % 64 bytes. */ md5_process_bytes (buffer, len, &ctx); /* Put result in desired memory area. */ return md5_finish_ctx (&ctx, resblock); } void md5_process_bytes (const void *buffer, size_t len, struct md5_ctx *ctx) { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy (&((char *) ctx->buffer)[left_over], buffer, add); ctx->buflen += add; if (ctx->buflen > 64) { md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); ctx->buflen &= 63; /* The regions in the following copy operation cannot overlap. */ memcpy (ctx->buffer, &((char *) ctx->buffer)[(left_over + add) & ~63], ctx->buflen); } buffer = (const char *) buffer + add; len -= add; } /* Process available complete blocks. */ if (len >= 64) { #if !_STRING_ARCH_unaligned # define alignof(type) offsetof (struct { char c; type x; }, x) # define UNALIGNED_P(p) (((size_t) p) % alignof (uint32_t) != 0) if (UNALIGNED_P (buffer)) while (len > 64) { md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); buffer = (const char *) buffer + 64; len -= 64; } else #endif { md5_process_block (buffer, len & ~63, ctx); buffer = (const char *) buffer + (len & ~63); len &= 63; } } /* Move remaining bytes in internal buffer. */ if (len > 0) { size_t left_over = ctx->buflen; memcpy (&((char *) ctx->buffer)[left_over], buffer, len); left_over += len; if (left_over >= 64) { md5_process_block (ctx->buffer, 64, ctx); left_over -= 64; memcpy (ctx->buffer, &ctx->buffer[16], left_over); } ctx->buflen = left_over; } } /* These are the four functions used in the four steps of the MD5 algorithm and defined in the RFC 1321. The first function is a little bit optimized (as found in Colin Plumbs public domain implementation). */ /* #define FF(b, c, d) ((b & c) | (~b & d)) */ #define FF(b, c, d) (d ^ (b & (c ^ d))) #define FG(b, c, d) FF (d, b, c) #define FH(b, c, d) (b ^ c ^ d) #define FI(b, c, d) (c ^ (b | ~d)) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. */ void md5_process_block (const void *buffer, size_t len, struct md5_ctx *ctx) { uint32_t correct_words[16]; const uint32_t *words = buffer; size_t nwords = len / sizeof (uint32_t); const uint32_t *endp = words + nwords; uint32_t A = ctx->A; uint32_t B = ctx->B; uint32_t C = ctx->C; uint32_t D = ctx->D; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; /* Process all bytes in the buffer with 64 bytes in each round of the loop. */ while (words < endp) { uint32_t *cwp = correct_words; uint32_t A_save = A; uint32_t B_save = B; uint32_t C_save = C; uint32_t D_save = D; /* First round: using the given function, the context and a constant the next context is computed. Because the algorithms processing unit is a 32-bit word and it is determined to work on words in little endian byte order we perhaps have to change the byte order before the computation. To reduce the work for the next steps we store the swapped words in the array CORRECT_WORDS. */ #define OP(a, b, c, d, s, T) \ do \ { \ a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ ++words; \ CYCLIC (a, s); \ a += b; \ } \ while (0) /* It is unfortunate that C does not provide an operator for cyclic rotation. Hope the C compiler is smart enough. */ #define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) /* Before we start, one word to the strange constants. They are defined in RFC 1321 as T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 Here is an equivalent invocation using Perl: perl -e 'foreach(1..64){printf "0x%08x\n", int (4294967296 * abs (sin $_))}' */ /* Round 1. */ OP (A, B, C, D, 7, 0xd76aa478); OP (D, A, B, C, 12, 0xe8c7b756); OP (C, D, A, B, 17, 0x242070db); OP (B, C, D, A, 22, 0xc1bdceee); OP (A, B, C, D, 7, 0xf57c0faf); OP (D, A, B, C, 12, 0x4787c62a); OP (C, D, A, B, 17, 0xa8304613); OP (B, C, D, A, 22, 0xfd469501); OP (A, B, C, D, 7, 0x698098d8); OP (D, A, B, C, 12, 0x8b44f7af); OP (C, D, A, B, 17, 0xffff5bb1); OP (B, C, D, A, 22, 0x895cd7be); OP (A, B, C, D, 7, 0x6b901122); OP (D, A, B, C, 12, 0xfd987193); OP (C, D, A, B, 17, 0xa679438e); OP (B, C, D, A, 22, 0x49b40821); /* For the second to fourth round we have the possibly swapped words in CORRECT_WORDS. Redefine the macro to take an additional first argument specifying the function to use. */ #undef OP #define OP(f, a, b, c, d, k, s, T) \ do \ { \ a += f (b, c, d) + correct_words[k] + T; \ CYCLIC (a, s); \ a += b; \ } \ while (0) /* Round 2. */ OP (FG, A, B, C, D, 1, 5, 0xf61e2562); OP (FG, D, A, B, C, 6, 9, 0xc040b340); OP (FG, C, D, A, B, 11, 14, 0x265e5a51); OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); OP (FG, A, B, C, D, 5, 5, 0xd62f105d); OP (FG, D, A, B, C, 10, 9, 0x02441453); OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); OP (FG, D, A, B, C, 14, 9, 0xc33707d6); OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); OP (FG, B, C, D, A, 8, 20, 0x455a14ed); OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); OP (FG, C, D, A, B, 7, 14, 0x676f02d9); OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); /* Round 3. */ OP (FH, A, B, C, D, 5, 4, 0xfffa3942); OP (FH, D, A, B, C, 8, 11, 0x8771f681); OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); OP (FH, B, C, D, A, 14, 23, 0xfde5380c); OP (FH, A, B, C, D, 1, 4, 0xa4beea44); OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); OP (FH, B, C, D, A, 6, 23, 0x04881d05); OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); /* Round 4. */ OP (FI, A, B, C, D, 0, 6, 0xf4292244); OP (FI, D, A, B, C, 7, 10, 0x432aff97); OP (FI, C, D, A, B, 14, 15, 0xab9423a7); OP (FI, B, C, D, A, 5, 21, 0xfc93a039); OP (FI, A, B, C, D, 12, 6, 0x655b59c3); OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); OP (FI, C, D, A, B, 10, 15, 0xffeff47d); OP (FI, B, C, D, A, 1, 21, 0x85845dd1); OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); OP (FI, C, D, A, B, 6, 15, 0xa3014314); OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); OP (FI, A, B, C, D, 4, 6, 0xf7537e82); OP (FI, D, A, B, C, 11, 10, 0xbd3af235); OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); OP (FI, B, C, D, A, 9, 21, 0xeb86d391); /* Add the starting values of the context. */ A += A_save; B += B_save; C += C_save; D += D_save; } /* Put checksum in context given as argument. */ ctx->A = A; ctx->B = B; ctx->C = C; ctx->D = D; } moc-2.6.0~svn-r3005/md5.h0000664000076400000500000001032511721557223013773 0ustar riesebiesrc/* -*- buffer-read-only: t -*- vi: set ro: */ /* DO NOT EDIT! GENERATED AUTOMATICALLY! */ /* Declaration of functions and data types used for MD5 sum computing library functions. Copyright (C) 1995-1997, 1999-2001, 2004-2006, 2008-2010 Free Software Foundation, Inc. This file is part of the GNU C Library. 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 3, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _MD5_H #define _MD5_H 1 #include #include #define MD5_DIGEST_SIZE 16 #define MD5_BLOCK_SIZE 64 #ifndef __GNUC_PREREQ # if defined __GNUC__ && defined __GNUC_MINOR__ # define __GNUC_PREREQ(maj, min) \ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) # else # define __GNUC_PREREQ(maj, min) 0 # endif #endif #ifndef __THROW # if defined __cplusplus && __GNUC_PREREQ (2,8) # define __THROW throw () # else # define __THROW # endif #endif #ifndef _LIBC # define __md5_buffer md5_buffer # define __md5_finish_ctx md5_finish_ctx # define __md5_init_ctx md5_init_ctx # define __md5_process_block md5_process_block # define __md5_process_bytes md5_process_bytes # define __md5_read_ctx md5_read_ctx # define __md5_stream md5_stream #endif # ifdef __cplusplus extern "C" { # endif /* Structure to save state of computation between the single steps. */ struct md5_ctx { uint32_t A; uint32_t B; uint32_t C; uint32_t D; uint32_t total[2]; uint32_t buflen; uint32_t buffer[32]; }; /* * The following three functions are build up the low level used in * the functions `md5_stream' and `md5_buffer'. */ /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ extern void __md5_init_ctx (struct md5_ctx *ctx) __THROW; /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void __md5_process_block (const void *buffer, size_t len, struct md5_ctx *ctx) __THROW; /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void __md5_process_bytes (const void *buffer, size_t len, struct md5_ctx *ctx) __THROW; /* Process the remaining bytes in the buffer and put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *__md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) __THROW; /* Put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *__md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) __THROW; /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ extern int __md5_stream (FILE *stream, void *resblock) __THROW; /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *__md5_buffer (const char *buffer, size_t len, void *resblock) __THROW; # ifdef __cplusplus } # endif #endif /* md5.h */ moc-2.6.0~svn-r3005/menu.c0000664000076400000500000004675013327217577014271 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2002 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "common.h" #include "options.h" #include "menu.h" #include "files.h" #include "rbtree.h" #include "utf8.h" /* Draw menu item on a given position from the top of the menu. */ static void draw_item (const struct menu *menu, const struct menu_item *mi, const int pos, const int item_info_pos, int title_space, const int number_space, const int draw_selected) { int title_width, queue_pos_len = 0; int ix, x; int y ATTR_UNUSED; /* OpenBSD flags this as unused. */ char buf[32]; assert (menu != NULL); assert (mi != NULL); assert (pos >= 0); assert (item_info_pos > menu->posx || (!menu->show_time && !menu->show_format)); assert (title_space > 0); assert (number_space == 0 || number_space >= 2); wmove (menu->win, pos, menu->posx); if (number_space) { if (draw_selected && mi == menu->selected && mi == menu->marked) wattrset (menu->win, menu->info_attr_sel_marked); else if (draw_selected && mi == menu->selected) wattrset (menu->win, menu->info_attr_sel); else if (mi == menu->marked) wattrset (menu->win, menu->info_attr_marked); else wattrset (menu->win, menu->info_attr_normal); xwprintw (menu->win, "%*d ", number_space - 1, mi->num + 1); } /* Set attributes */ if (draw_selected && mi == menu->selected && mi == menu->marked) wattrset (menu->win, mi->attr_sel_marked); else if (draw_selected && mi == menu->selected) wattrset (menu->win, mi->attr_sel); else if (mi == menu->marked) wattrset (menu->win, mi->attr_marked); else wattrset (menu->win, mi->attr_normal); /* Compute the length of the queue position if nonzero */ if (mi->queue_pos) { sprintf (buf, "%d", mi->queue_pos); queue_pos_len = strlen(buf) + 2; title_space -= queue_pos_len; } title_width = strwidth (mi->title); getyx (menu->win, y, x); if (title_width <= title_space || mi->align == MENU_ALIGN_LEFT) xwaddnstr (menu->win, mi->title, title_space); else { char *ptr; ptr = xstrtail (mi->title, title_space); xwaddstr (menu->win, ptr); free (ptr); } /* Fill the remainder of the title field with spaces. */ if (mi == menu->selected) { getyx (menu->win, y, ix); while (ix < x + title_space) { waddch (menu->win, ' '); ix += 1; } } /* Description. */ if (draw_selected && mi == menu->selected && mi == menu->marked) wattrset (menu->win, menu->info_attr_sel_marked); else if (draw_selected && mi == menu->selected) wattrset (menu->win, menu->info_attr_sel); else if (mi == menu->marked) wattrset (menu->win, menu->info_attr_marked); else wattrset (menu->win, menu->info_attr_normal); wmove (menu->win, pos, item_info_pos - queue_pos_len); /* Position in queue. */ if (mi->queue_pos) { xwaddstr (menu->win, "["); xwaddstr (menu->win, buf); xwaddstr (menu->win, "]"); } if (menu->show_time && menu->show_format && (*mi->time || *mi->format)) xwprintw (menu->win, "[%5s|%3s]", mi->time, mi->format); else if (menu->show_time && mi->time[0]) xwprintw (menu->win, "[%5s]", mi->time); else if (menu->show_format && mi->format[0]) xwprintw (menu->win, "[%3s]", mi->format); } void menu_draw (const struct menu *menu, const int active) { struct menu_item *mi; int title_width; int info_pos; int number_space = 0; assert (menu != NULL); if (menu->number_items) { int count = menu->nitems / 10; number_space = 2; /* begin from 1 digit and a space char */ while (count) { count /= 10; number_space++; } } else number_space = 0; if (menu->show_time || menu->show_format) { title_width = menu->width - 2; /* -2 for brackets */ if (menu->show_time) title_width -= 5; /* 00:00 */ if (menu->show_format) title_width -= 3; /* MP3 */ if (menu->show_time && menu->show_format) title_width--; /* for | */ info_pos = title_width; } else { title_width = menu->width; info_pos = title_width; } title_width -= number_space; for (mi = menu->top; mi && mi->num - menu->top->num < menu->height; mi = mi->next) draw_item (menu, mi, mi->num - menu->top->num + menu->posy, menu->posx + info_pos, title_width, number_space, active); } /* Move the cursor to the selected file. */ void menu_set_cursor (const struct menu *m) { assert (m != NULL); if (m->selected) wmove (m->win, m->selected->num - m->top->num + m->posy, m->posx); } static int rb_compare (const void *a, const void *b, const void *unused ATTR_UNUSED) { struct menu_item *mia = (struct menu_item *)a; struct menu_item *mib = (struct menu_item *)b; return strcmp (mia->file, mib->file); } static int rb_fname_compare (const void *key, const void *data, const void *unused ATTR_UNUSED) { const char *fname = (const char *)key; const struct menu_item *mi = (const struct menu_item *)data; return strcmp (fname, mi->file); } /* menu_items must be malloc()ed memory! */ struct menu *menu_new (WINDOW *win, const int posx, const int posy, const int width, const int height) { struct menu *menu; assert (win != NULL); assert (posx >= 0); assert (posy >= 0); assert (width > 0); assert (height > 0); menu = (struct menu *)xmalloc (sizeof(struct menu)); menu->win = win; menu->items = NULL; menu->nitems = 0; menu->top = NULL; menu->last = NULL; menu->selected = NULL; menu->posx = posx; menu->posy = posy; menu->width = width; menu->height = height; menu->marked = NULL; menu->show_time = 0; menu->show_format = false; menu->info_attr_normal = A_NORMAL; menu->info_attr_sel = A_NORMAL; menu->info_attr_marked = A_NORMAL; menu->info_attr_sel_marked = A_NORMAL; menu->number_items = 0; menu->search_tree = rb_tree_new (rb_compare, rb_fname_compare, NULL); return menu; } struct menu_item *menu_add (struct menu *menu, const char *title, const enum file_type type, const char *file) { struct menu_item *mi; assert (menu != NULL); assert (title != NULL); mi = (struct menu_item *)xmalloc (sizeof(struct menu_item)); mi->title = xstrdup (title); mi->type = type; mi->file = xstrdup (file); mi->num = menu->nitems; mi->attr_normal = A_NORMAL; mi->attr_sel = A_NORMAL; mi->attr_marked = A_NORMAL; mi->attr_sel_marked = A_NORMAL; mi->align = MENU_ALIGN_LEFT; mi->time[0] = 0; mi->format[0] = 0; mi->queue_pos = 0; mi->next = NULL; mi->prev = menu->last; if (menu->last) menu->last->next = mi; if (!menu->items) menu->items = mi; if (!menu->top) menu->top = menu->items; if (!menu->selected) menu->selected = menu->items; if (file) rb_insert (menu->search_tree, (void *)mi); menu->last = mi; menu->nitems++; return mi; } static struct menu_item *menu_add_from_item (struct menu *menu, const struct menu_item *mi) { struct menu_item *new; assert (menu != NULL); assert (mi != NULL); new = menu_add (menu, mi->title, mi->type, mi->file); new->attr_normal = mi->attr_normal; new->attr_sel = mi->attr_sel; new->attr_marked = mi->attr_marked; new->attr_sel_marked = mi->attr_sel_marked; strncpy(new->time, mi->time, FILE_TIME_STR_SZ); strncpy(new->format, mi->format, FILE_FORMAT_SZ); return new; } static struct menu_item *get_item_relative (struct menu_item *mi, int to_move) { assert (mi != NULL); while (to_move) { struct menu_item *prev = mi; if (to_move > 0) { mi = mi->next; to_move--; } else { mi = mi->prev; to_move++; } if (!mi) { mi = prev; break; } } return mi; } void menu_update_size (struct menu *menu, const int posx, const int posy, const int width, const int height) { assert (menu != NULL); assert (posx >= 0); assert (posy >= 0); assert (width > 0); assert (height > 0); menu->posx = posx; menu->posy = posy; menu->width = width; menu->height = height; if (menu->selected && menu->top && menu->selected->num >= menu->top->num + menu->height) menu->selected = get_item_relative (menu->top, menu->height - 1); } static void menu_item_free (struct menu_item *mi) { assert (mi != NULL); assert (mi->title != NULL); free (mi->title); if (mi->file) free (mi->file); free (mi); } void menu_free (struct menu *menu) { struct menu_item *mi; assert (menu != NULL); mi = menu->items; while (mi) { struct menu_item *next = mi->next; menu_item_free (mi); mi = next; } rb_tree_free (menu->search_tree); free (menu); } void menu_driver (struct menu *menu, const enum menu_request req) { assert (menu != NULL); if (menu->nitems == 0) return; if (req == REQ_DOWN && menu->selected->next) { menu->selected = menu->selected->next; if (menu->selected->num >= menu->top->num + menu->height) { menu->top = get_item_relative (menu->selected, -menu->height / 2); if (menu->top->num > menu->nitems - menu->height) menu->top = get_item_relative (menu->last, -menu->height + 1); } } else if (req == REQ_UP && menu->selected->prev) { menu->selected = menu->selected->prev; if (menu->top->num > menu->selected->num) menu->top = get_item_relative (menu->selected, -menu->height / 2); } else if (req == REQ_PGDOWN && menu->selected->num < menu->nitems - 1) { if (menu->selected->num + menu->height - 1 < menu->nitems - 1) { menu->selected = get_item_relative (menu->selected, menu->height - 1); menu->top = get_item_relative (menu->top, menu->height - 1); if (menu->top->num > menu->nitems - menu->height) menu->top = get_item_relative (menu->last, -menu->height + 1); } else { menu->selected = menu->last; menu->top = get_item_relative (menu->last, -menu->height + 1); } } else if (req == REQ_PGUP && menu->selected->prev) { if (menu->selected->num - menu->height + 1 > 0) { menu->selected = get_item_relative (menu->selected, -menu->height + 1); menu->top = get_item_relative (menu->top, -menu->height + 1); } else { menu->selected = menu->items; menu->top = menu->items; } } else if (req == REQ_TOP) { menu->selected = menu->items; menu->top = menu->items; } else if (req == REQ_BOTTOM) { menu->selected = menu->last; menu->top = get_item_relative (menu->selected, -menu->height + 1); } } /* Return the index of the currently selected item. */ struct menu_item *menu_curritem (struct menu *menu) { assert (menu != NULL); return menu->selected; } static void make_item_visible (struct menu *menu, struct menu_item *mi) { assert (menu != NULL); assert (mi != NULL); if (mi->num < menu->top->num || mi->num >= menu->top->num + menu->height) { menu->top = get_item_relative(mi, -menu->height/2); if (menu->top->num > menu->nitems - menu->height) menu->top = get_item_relative (menu->last, -menu->height + 1); } if (menu->selected) { if (menu->selected->num < menu->top->num || menu->selected->num >= menu->top->num + menu->height) menu->selected = mi; } } /* Make this item selected */ static void menu_setcurritem (struct menu *menu, struct menu_item *mi) { assert (menu != NULL); assert (mi != NULL); menu->selected = mi; make_item_visible (menu, mi); } /* Make the item with this title selected. */ void menu_setcurritem_title (struct menu *menu, const char *title) { struct menu_item *mi; /* Find it */ for (mi = menu->top; mi; mi = mi->next) if (!strcmp(mi->title, title)) break; if (mi) menu_setcurritem (menu, mi); } static struct menu_item *menu_find_by_position (struct menu *menu, const int num) { struct menu_item *mi; assert (menu != NULL); mi = menu->top; while (mi && mi->num != num) mi = mi->next; return mi; } void menu_set_state (struct menu *menu, const struct menu_state *st) { assert (menu != NULL); if (!(menu->selected = menu_find_by_position(menu, st->selected_item))) menu->selected = menu->last; if (!(menu->top = menu_find_by_position(menu, st->top_item))) menu->top = get_item_relative (menu->last, menu->height + 1); } void menu_set_items_numbering (struct menu *menu, const int number) { assert (menu != NULL); menu->number_items = number; } void menu_get_state (const struct menu *menu, struct menu_state *st) { assert (menu != NULL); st->top_item = menu->top ? menu->top->num : -1; st->selected_item = menu->selected ? menu->selected->num : -1; } void menu_unmark_item (struct menu *menu) { assert (menu != NULL); menu->marked = NULL; } /* Make a new menu from elements matching pattern. */ struct menu *menu_filter_pattern (const struct menu *menu, const char *pattern) { struct menu *new; const struct menu_item *mi; assert (menu != NULL); assert (pattern != NULL); new = menu_new (menu->win, menu->posx, menu->posy, menu->width, menu->height); menu_set_show_time (new, menu->show_time); menu_set_show_format (new, menu->show_format); menu_set_info_attr_normal (new, menu->info_attr_normal); menu_set_info_attr_sel (new, menu->info_attr_sel); menu_set_info_attr_marked (new, menu->info_attr_marked); menu_set_info_attr_sel_marked (new, menu->info_attr_sel_marked); for (mi = menu->items; mi; mi = mi->next) if (strcasestr(mi->title, pattern)) menu_add_from_item (new, mi); if (menu->marked) menu_mark_item (new, menu->marked->file); return new; } void menu_item_set_attr_normal (struct menu_item *mi, const int attr) { assert (mi != NULL); mi->attr_normal = attr; } void menu_item_set_attr_sel (struct menu_item *mi, const int attr) { assert (mi != NULL); mi->attr_sel = attr; } void menu_item_set_attr_sel_marked (struct menu_item *mi, const int attr) { assert (mi != NULL); mi->attr_sel_marked = attr; } void menu_item_set_attr_marked (struct menu_item *mi, const int attr) { assert (mi != NULL); mi->attr_marked = attr; } void menu_item_set_time (struct menu_item *mi, const char *time) { assert (mi != NULL); mi->time[sizeof(mi->time)-1] = 0; strncpy (mi->time, time, sizeof(mi->time)); assert (mi->time[sizeof(mi->time)-1] == 0); } void menu_item_set_format (struct menu_item *mi, const char *format) { assert (mi != NULL); assert (format != NULL); mi->format[sizeof(mi->format)-1] = 0; strncpy (mi->format, format, sizeof(mi->format)); assert (mi->format[sizeof(mi->format)-1] == 0); } void menu_item_set_queue_pos (struct menu_item *mi, const int pos) { assert (mi != NULL); mi->queue_pos = pos; } void menu_set_show_time (struct menu *menu, const int t) { assert (menu != NULL); menu->show_time = t; } void menu_set_show_format (struct menu *menu, const bool t) { assert (menu != NULL); menu->show_format = t; } void menu_set_info_attr_normal (struct menu *menu, const int attr) { assert (menu != NULL); menu->info_attr_normal = attr; } void menu_set_info_attr_sel (struct menu *menu, const int attr) { assert (menu != NULL); menu->info_attr_sel = attr; } void menu_set_info_attr_marked (struct menu *menu, const int attr) { assert (menu != NULL); menu->info_attr_marked = attr; } void menu_set_info_attr_sel_marked (struct menu *menu, const int attr) { assert (menu != NULL); menu->info_attr_sel_marked = attr; } enum file_type menu_item_get_type (const struct menu_item *mi) { assert (mi != NULL); return mi->type; } char *menu_item_get_file (const struct menu_item *mi) { assert (mi != NULL); return xstrdup (mi->file); } void menu_item_set_title (struct menu_item *mi, const char *title) { assert (mi != NULL); if (mi->title) free (mi->title); mi->title = xstrdup (title); } int menu_nitems (const struct menu *menu) { assert (menu != NULL); return menu->nitems; } struct menu_item *menu_find (struct menu *menu, const char *fname) { struct rb_node *x; assert (menu != NULL); assert (fname != NULL); x = rb_search (menu->search_tree, fname); if (rb_is_null(x)) return NULL; return (struct menu_item *)rb_get_data (x); } void menu_mark_item (struct menu *menu, const char *file) { struct menu_item *item; assert (menu != NULL); assert (file != NULL); item = menu_find (menu, file); if (item) menu->marked = item; } static void menu_renumber_items (struct menu *menu) { int i = 0; struct menu_item *mi; assert (menu != NULL); for (mi = menu->items; mi; mi = mi->next) mi->num = i++; assert (i == menu->nitems); } static void menu_delete (struct menu *menu, struct menu_item *mi) { assert (menu != NULL); assert (mi != NULL); if (mi->prev) mi->prev->next = mi->next; if (mi->next) mi->next->prev = mi->prev; if (menu->items == mi) menu->items = mi->next; if (menu->last == mi) menu->last = mi->prev; if (menu->marked == mi) menu->marked = NULL; if (menu->selected == mi) menu->selected = mi->next ? mi->next : mi->prev; if (menu->top == mi) menu->top = mi->next ? mi->next : mi->prev; if (mi->file) rb_delete (menu->search_tree, mi->file); menu->nitems--; menu_renumber_items (menu); menu_item_free (mi); } void menu_del_item (struct menu *menu, const char *fname) { struct menu_item *mi; assert (menu != NULL); assert (fname != NULL); mi = menu_find (menu, fname); assert (mi != NULL); menu_delete (menu, mi); } void menu_item_set_align (struct menu_item *mi, const enum menu_align align) { assert (mi != NULL); mi->align = align; } void menu_setcurritem_file (struct menu *menu, const char *file) { struct menu_item *mi; assert (menu != NULL); assert (file != NULL); mi = menu_find (menu, file); if (mi) menu_setcurritem (menu, mi); } /* Return non-zero value if the item in in the visible part of the menu. */ int menu_is_visible (const struct menu *menu, const struct menu_item *mi) { assert (menu != NULL); assert (mi != NULL); if (mi->num >= menu->top->num && mi->num < menu->top->num + menu->height) return 1; return 0; } static void menu_items_swap (struct menu *menu, struct menu_item *mi1, struct menu_item *mi2) { int t; assert (menu != NULL); assert (mi1 != NULL); assert (mi2 != NULL); assert (mi1 != mi2); /* if they are next to each other, change the pointers so that mi2 * is the second one */ if (mi2->next == mi1) { struct menu_item *i = mi1; mi1 = mi2; mi2 = i; } if (mi1->next == mi2) { if (mi2->next) mi2->next->prev = mi1; if (mi1->prev) mi1->prev->next = mi2; mi1->next = mi2->next; mi2->prev = mi1->prev; mi1->prev = mi2; mi2->next = mi1; } else { if (mi2->next) mi2->next->prev = mi1; if (mi2->prev) mi2->prev->next = mi1; mi2->next = mi1->next; mi2->prev = mi1->prev; if (mi1->next) mi1->next->prev = mi2; if (mi1->prev) mi1->prev->next = mi2; mi1->next = mi2->next; mi1->prev = mi2->prev; } t = mi1->num; mi1->num = mi2->num; mi2->num = t; if (menu->top == mi1) menu->top = mi2; else if (menu->top == mi2) menu->top = mi1; if (menu->last == mi1) menu->last = mi2; else if (menu->last == mi2) menu->last = mi1; if (menu->items == mi1) menu->items = mi2; else if (menu->items == mi2) menu->items = mi1; } void menu_swap_items (struct menu *menu, const char *file1, const char *file2) { struct menu_item *mi1, *mi2; assert (menu != NULL); assert (file1 != NULL); assert (file2 != NULL); if ((mi1 = menu_find(menu, file1)) && (mi2 = menu_find(menu, file2)) && mi1 != mi2) { menu_items_swap (menu, mi1, mi2); /* make sure that the selected item is visible */ menu_setcurritem (menu, menu->selected); } } /* Make sure that this file is visible in the menu. */ void menu_make_visible (struct menu *menu, const char *file) { struct menu_item *mi; assert (menu != NULL); assert (file != NULL); if ((mi = menu_find(menu, file))) make_item_visible (menu, mi); } moc-2.6.0~svn-r3005/menu.h0000664000076400000500000001127712424322506014254 0ustar riesebiesrc#ifndef MENU_H #define MENU_H #if defined HAVE_NCURSESW_CURSES_H # include #elif defined HAVE_NCURSESW_H # include #elif defined HAVE_NCURSES_CURSES_H # include #elif defined HAVE_NCURSES_H # include #elif defined HAVE_CURSES_H # include #endif #include "files.h" #include "rbtree.h" #ifdef __cplusplus extern "C" { #endif enum menu_request { REQ_UP, REQ_DOWN, REQ_PGUP, REQ_PGDOWN, REQ_TOP, REQ_BOTTOM }; enum menu_align { MENU_ALIGN_RIGHT, MENU_ALIGN_LEFT }; #define FILE_TIME_STR_SZ 6 #define FILE_FORMAT_SZ 4 struct menu_item { char *title; /* Title of the item */ enum menu_align align; /* Align of the title */ int num; /* Position of the item starting from 0. */ /* Curses attributes in different states: */ int attr_normal; int attr_sel; int attr_marked; int attr_sel_marked; /* Associated file: */ char *file; enum file_type type; /* Additional information shown: */ char time[FILE_TIME_STR_SZ]; /* File time string */ char format[FILE_FORMAT_SZ]; /* File format */ int queue_pos; /* Position in the queue */ struct menu_item *next; struct menu_item *prev; }; struct menu { WINDOW *win; struct menu_item *items; int nitems; /* number of present items */ struct menu_item *top; /* first visible item */ struct menu_item *last; /* last item in the menu */ /* position and size */ int posx; int posy; int width; int height; struct menu_item *selected; /* selected item */ struct menu_item *marked; /* index of the marked item or -1 */ /* Flags for displaying information about the file. */ int show_time; bool show_format; int info_attr_normal; /* attributes for information about the file */ int info_attr_sel; int info_attr_marked; int info_attr_sel_marked; int number_items; /* display item number (position) */ struct rb_tree *search_tree; /* RB tree for searching by file name */ }; /* Menu state: relative (to the first item) positions of the top and selected * items. */ struct menu_state { int top_item; int selected_item; }; struct menu *menu_new (WINDOW *win, const int posx, const int posy, const int width, const int height); struct menu_item *menu_add (struct menu *menu, const char *title, const enum file_type type, const char *file); void menu_item_set_attr_normal (struct menu_item *mi, const int attr); void menu_item_set_attr_sel (struct menu_item *mi, const int attr); void menu_item_set_attr_sel_marked (struct menu_item *mi, const int attr); void menu_item_set_attr_marked (struct menu_item *mi, const int attr); void menu_item_set_time (struct menu_item *mi, const char *time); void menu_item_set_format (struct menu_item *mi, const char *format); void menu_item_set_queue_pos (struct menu_item *mi, const int pos); void menu_free (struct menu *menu); void menu_driver (struct menu *menu, const enum menu_request req); void menu_setcurritem_title (struct menu *menu, const char *title); void menu_setcurritem_file (struct menu *menu, const char *file); void menu_draw (const struct menu *menu, const int active); void menu_mark_item (struct menu *menu, const char *file); void menu_set_state (struct menu *menu, const struct menu_state *st); void menu_get_state (const struct menu *menu, struct menu_state *st); void menu_update_size (struct menu *menu, const int posx, const int posy, const int width, const int height); void menu_unmark_item (struct menu *menu); struct menu *menu_filter_pattern (const struct menu *menu, const char *pattern); void menu_set_show_time (struct menu *menu, const int t); void menu_set_show_format (struct menu *menu, const bool t); void menu_set_info_attr_normal (struct menu *menu, const int attr); void menu_set_info_attr_sel (struct menu *menu, const int attr); void menu_set_info_attr_marked (struct menu *menu, const int attr); void menu_set_info_attr_sel_marked (struct menu *menu, const int attr); void menu_set_items_numbering (struct menu *menu, const int number); enum file_type menu_item_get_type (const struct menu_item *mi); char *menu_item_get_file (const struct menu_item *mi); struct menu_item *menu_curritem (struct menu *menu); void menu_item_set_title (struct menu_item *mi, const char *title); int menu_nitems (const struct menu *menu); struct menu_item *menu_find (struct menu *menu, const char *fname); void menu_del_item (struct menu *menu, const char *fname); void menu_item_set_align (struct menu_item *mi, const enum menu_align align); int menu_is_visible (const struct menu *menu, const struct menu_item *mi); void menu_swap_items (struct menu *menu, const char *file1, const char *file2); void menu_make_visible (struct menu *menu, const char *file); void menu_set_cursor (const struct menu *m); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/mocp.10000664000076400000500000003462013454525373014167 0ustar riesebiesrc.\" Start example. .de EX . nr mE \\n(.f . nf . nh . ft CW .. . .\" End example. .de EE . ft \\n(mE . fi . hy \\n(HY .. . .TH MOC 1 "16 November 2016" "Version 2.6-alpha3" "Music On Console" .ad l .SH NAME MOC \- Console audio player .LP .SH SYNOPSIS \fBmocp\fR [\fIOPTIONS\fR] [\fIFILE\fR|\fIDIR\fR ...] .LP .SH DESCRIPTION MOC is a console audio player with simple ncurses interface. It supports OGG, WAV, MP3 and other formats. Just run \fBmocp\fP, go to some directory using the menu and press enter to start playing the file. The program will automatically play the rest of the files in the directory. .LP With no options and no file arguments the program begins in current directory, or in \fBMusicDir\fP if the \fBStartInMusicDir\fP option is set in the configuration file. If you give a directory on the command line, MOC will try to go there. If a playlist is given, then it is loaded. With multiple files, playlists or directories, everything will be added to the playlist recursively (including the contents of any playlist given). (Note that relative paths in playlists are resolved with respect to the directory of the playlist, or of the symlink being used to reference it.) .LP .SH OPTIONS If an option can also be set in the configuration file the command line overrides it (but see the \fB\-O\fP option for the list-valued configuration file options exception). .LP .TP \fB\-D\fP, \fB\-\-debug\fP Run MOC in debug mode. The client and server log a lot of information to debug files. Don't use this; the server log is large. This is only available if MOC was compiled without \fB\-\-disable\-debug\fP. .LP .TP \fB\-S\fP, \fB\-\-server\fP Run only the server and exit. .LP .TP \fB\-F\fP, \fB\-\-foreground\fP Implies \fB\-S\fP. Run the server in foreground and log everything to stdout. .LP .TP \fB\-R\fP \fINAME\fP[\fB:\fP...], \ \fB\-\-sound\-driver\fP \fINAME\fP[\fB:\fP...] Use the specified sound driver(s). They can be \fBOSS\fP, \fBALSA\fP, \fBJACK\fP, \fBSNDIO\fP or \fBnull\fP (for debugging). Some of the drivers may not have been compiled in. This option is called \fBSoundDriver\fP in the configuration file. .LP .TP \fB\-m\fP, \fB\-\-music\-dir\fP Start in \fBMusicDir\fP (set in the configuration file). This can be also set in the configuration file as \fBStartInMusicDir\fP. .LP .TP \fB\-q\fP, \fB\-\-enqueue\fP Add files given after command line options to the queue. Don't start the interface. .LP .TP \fB\-a\fP, \fB\-\-append\fP Append files, directories (recursively) and playlists given after command line options to the playlist. Don't start the interface. .LP .TP \fB\-c\fP, \fB\-\-clear\fP Clear the playlist. .LP .TP \fB\-p\fP, \fB\-\-play\fP Start playing from the first item on the playlist. .LP .TP \fB\-l\fP, \fB\-\-playit\fP Play files given on the command line without modifying the clients' playlists. .LP .TP \fB\-f\fP, \fB\-\-next\fP Request playing the next song from the server's playlist. .LP .TP \fB\-r\fP, \fB\-\-previous\fP Request playing the previous song from the server's playlist. .LP .TP \fB\-s\fP, \fB\-\-stop\fP Request the server to stop playing. .LP .TP \fB\-x\fP, \fB\-\-exit\fP Bring down the server. .LP .TP \fB\-P\fP, \fB\-\-pause\fP Request the server to pause playing. .LP .TP \fB\-U\fP, \fB\-\-unpause\fP Request the server to resume playing when paused. .LP .TP \fB\-G\fP, \fB\-\-toggle\-pause\fP Toggle between play and pause. .LP .TP \fB\-k\fP [\fB+\fP|\fB\-\fP]\fIN\fP, \ \fB\-\-seek \fP[\fB+\fP|\fB\-\fP]\fIN\fP Seek forward (positive) or backward (negative) by \fIN\fP seconds in the file currently being played. .LP .TP \fB\-T\fP \fITHEME\fP, \fB\-\-theme\fP \fITHEME\fP Use a theme file. If the path is not absolute, the file will be searched for in \fB/usr/share/moc/themes/\fP (depends on installation prefix), \fB~/.moc/themes/\fP and the current directory. .LP .TP \fB\-C\fP \fIFILE\fP, \fB\-\-config\fP \fIFILE\fP Use the specified configuration file (which must be readable) instead of the default. As this file can specify commands which invoke other applications MOC will refuse to start if it is not owned by either root or the current user, or if it is writable by anyone other than its owner. .LP .TP \fB\-\-no\-config\fP Do not read any configuration file but use the built-in defaults. .LP .TP \fB\-O\fP \fINAME\fP[\fB+\fP]\fB=\fP\fIVALUE\fP, \ \fB\-\-set\-option\fP \fINAME\fP[\fB+\fP]\fB=\fP\fIVALUE\fP Override configuration file option NAME with VALUE. This option can be repeated as many times as needed and the option name is not case sensitive. Most option values are set before the configuration file is processed (which allows the new values to be picked up by substitutions); however, list-valued options are overridden afterwards (which gives the choice of whether the configured values are replaced or added to). .IP See the example configuration file (\fBconfig.example\fP) for a description of the options available. .LP .RS .EX Examples: \fB\-O AutoNext=no\fP \fB\-O messagelingertime=1 \-O XTerms+=xxt:xwt\fP .EE .RE .IP Note that MOC does not perform variable substitution as it does for values read from the configuration file. .LP .TP \fB\-M\fP \fIDIR\fP, \fB\-\-moc\-dir\fP \fIDIR\fP Use the specified MOC directory instead of the default. This also causes the configuration file from that directory to be used. This can also be specified in the configuration file using the \fBMOCDir\fP option. .LP .TP \fB\-y\fP, \fB\-\-sync\fP This copy of the interface will synchronize its playlist with other clients. This option is called \fBSyncPlaylist\fP in the configuration file. .LP .TP \fB\-n\fP, \fB\-\-nosync\fP This copy of the interface will not synchronize its playlist with other clients (see above). .LP .TP \fB\-A\fP, \fB\-\-ascii\fP Use ASCII characters to draw lines. (This helps on some terminals.) .LP .TP \fB\-i\fP, \fB\-\-info\fP Print the information about the file currently being played. .LP .TP \fB\-Q\fP \fIFORMAT_STRING\fP, \fB\-\-format\fP \fIFORMAT_STRING\fP Print information about the file currently being played using a format string. Replace string sequences with the actual information: .IP .RS 16 .EX \fB%state\fP State \fB%file\fP File \fB%title\fP Title \fB%artist\fP Artist \fB%song\fP SongTitle \fB%album\fP Album \fB%tt\fP TotalTime \fB%tl\fP TimeLeft \fB%ts\fP TotalSec \fB%ct\fP CurrentTime \fB%cs\fP CurrentSec \fB%b\fP Bitrate \fB%r\fP Rate .EE .RE .IP It is also possible to use variables from the \fBFormatString\fP configuration file option. .LP .TP \fB\-e\fP, \fB\-\-recursively\fP Alias of \fB\-a\fP for backward compatibility. .LP .TP \fB\-h\fP, \fB\-\-help\fP Print a list of options with short descriptions and exit. .LP .TP \fB\-\-usage\fP Print a synopsis of the mocp command and exit. .LP .TP \fB\-V\fP, \fB\-\-version\fP Print the program version and exit. .LP .TP \fB\-\-echo-args\fP Print the POPT-interpreted command line arguments and exit. .LP .TP \fB\-v\fP [\fB+\fP|\fB\-\fP]\fIN\fP, \ \fB\-\-volume\fP [\fB+\fP|\fB\-\fP]\fIN\fP Adjust the mixer volume. You can set (\fB\-v 50\fP) or adjust (\fB\-v +10\fP, \fB\-v \-10\fP). .LP .TP \fB\-t\fP \fIOPTION\fP[\fB,\fP...], \fB\-\-toggle\fP \fIOPTION\fP[\fB,\fP...] .TQ \fB\-o\fP \fIOPTION\fP[\fB,\fP...], \fB\-\-on\fP \fIOPTION\fP[\fB,\fP...] .TQ \fB\-u\fP \fIOPTION\fP[\fB,\fP...], \fB\-\-off\fP \fIOPTION\fP[\fB,\fP...] Followed by a list of identifiers, these will control MOC's playlist options. Valid identifiers are \fBshuffle\fP, \fBrepeat\fP and \fBautonext\fP. They can be shortened to '\fBs\fP', '\fBr\fP' and '\fBn\fP' respectively. Both the identifiers and short forms are case insensitive. .LP .RS .EX Example: \fB\-t shuffle,R,n\fP .EE would toggle shuffle, repeat and autonext all at once. .RE .LP .TP \fB\-j\fP \fIN\fP{\fBs\fP|\fB%\fP}, \fB\-\-jump\fP \fIN\fP{\fBs\fP|\fB%\fP} Jump to some position in the current file. \fIN\fP is the number of seconds (when followed by an '\fBs\fP') or the percent of total file time (when followed by a '\fB%\fP'). .LP .RS .EX Examples: \fB\-j 10s\fP, \fB\-j 50%\fP .EE .RE .LP .SH USING POPT ALIASES MOC uses the POPT library to process its command line. This allows users to assign MOC options and arguments to an alias of their choosing. The aliases are just lines in the \fB~/.popt\fP text file and have the general form: .IP .RS .EX .BI mocp " " alias " " \fInewoption\fP " " \fIexpansion\fP .EE .RE .LP This works as if \fIexpansion\fP textually replaces \fInewoption\fP on the command line. The replacement is recursive; that is, other \fInewoption\fPs can be embedded in the \fIexpansion\fP. The \fIexpansion\fP is parsed similarly to a shell command, which allows \\, ", and ' to be used for quoting. If a backslash is the final character on a line, the next line in the file is assumed to be a logical continuation of the line containing the backslash, just as in the shell. The \fInewoption\fP can be either a short or long option, and any syntactically valid name the user wishes to use. .LP If you add a description for the new option and/or for any argument by appending the special POPT options \fB\-\-POPTdesc\fP and \fB\-\-POPTargs\fP, then the option will be displayed in the output of \fB\-\-help\fP and \fB\-\-usage\fP. The value for these two options are strings of the form \fB$"\fP\fIstring\fP\fB"\fP. .LP So, for example: .IP .RS .EX \fBmocp alias \-\-single \-D \-\-set\-option autonext=no \\\fP \fB \-\-POPTdesc=$"Play just the file selected" .EE .RE .LP would allow the user to turn on logging (\fB\-D\fP) and override the configuration file's \fBAutoNext\fP option setting just by using \fB\-\-single\fP as an option to the mocp command. .LP Sometimes you may wish to provide values to aliased options from the command line. If just one aliased option has such a value, then it's a simple matter of placing it last: .LP .IP .RS .EX \fBmocp alias \-\-yours \-\-sound-driver OSS \-\-theme .EE .RE .LP when used like this: .LP .IP .RS .EX \fBmocp \-\-yours your_theme .EE .RE .LP would result in: .LP .IP .RS .EX \fBmocp \-\-sound-driver OSS \-\-theme your_theme .EE .RE .LP But aliasing multiple options with such values means making use of the special construct \fB!#:+\fP (and quoting carefully): .LP .IP .RS .EX \fBmocp alias \-1 "\-R !#:+" "\-T my_theme" "\-O !#:+" .EE .RE .LP when used like this: .LP .IP .RS .EX \fBmocp \-1 OSS shuffle=yes ~/my_music .EE .RE .LP would result in: .LP .IP .RS .EX \fBmocp \-R OSS \-T my_theme \-O shuffle=yes ~/my_music .EE .RE .LP There is also a \fB~/.popt\fP entry which allows for the execution of a different program when the associated option is used. For this, an \fBexec\fP is used in place of the \fBalias\fP and the \fIexpansion\fP is the program to be executed: .LP .IP .RS .EX \fBmocp exec \-\-help /usr/bin/man 1 mocp \\ \fB POPTdesc=$"Provide the man page instead of help" .EE .RE .LP This would override the usual MOC \fB\-\-help\fP output and use the system's \fBman\fP program to present this man page instead. .LP Note that while \fB~/.popt\fP (or \fB/etc/popt\fP) is the default POPT configuration file, you can nominate specific file(s) to be used instead via the \fBMOCP_POPTRC\fP environment variable. .LP .SH ENVIRONMENT VARIABLES The following environment variables are used directly by MOC. Additional variables may be relevant to the libraries MOC uses. Also, any environment environment variable may be substituted into a configuration file option value (see the 'config.example' file for details). .LP .TP .B ESCDELAY An ncurses(3X) variable which specifies the delay (in milliseconds) after which it will treat an ESC as a standalone key and not part of an escaped character sequence (such as is generated by function keys). MOC sets this value to 25ms by default, which is sufficient for most systems. .LP .TP .B HOME Tells MOC where your home directory is located and is used for various purposes, including the default location of the MOC directory. .LP .TP .B MOCP_OPTS The value of this variable will be prepended to the command line options before they are processed. .LP .TP .B MOCP_POPTRC A colon-separated list of POPT configuration files which will be loaded in sequence by MOC during initialisation. If the variable is unset then the default POPT configuration file will be used. If the variable is set but empty then no POPT configuration file will be loaded. If the variable is set then those files which exist will be loaded and those which don't will be skipped. .IP As these files can specify commands which invoke other applications, MOC will refuse to start if they are not owned by root or the current user, or they are writable by anyone other than their owner. .LP .TP .B TERM \fPand\fB WINDOW Used by MOC to distinguish between X-terminals, screen(1) and console terminals. MOC uses the configuration file options \fBXTerms\fP and \fBScreenTerms\fP to help make this determination. .LP .SH FILES .TP .B ~/.moc MOC directory for the configuration file, socket, the pid file and other data. .LP .TP .B ~/.moc/config Configuration file for MOC. The format is very simple; to see how to use it look at the example configuration file (\fBconfig.example\fP) distributed with the program. The example file fully describes all the configuration options, and so is a useful reference when using the \fB\-O\fP option. As this file can specify commands which invoke other applications MOC will refuse to start if it is not owned by either root or the current user, or if it is writable by anyone other than its owner. .LP .TP .B ~/.popt .TQ .B /etc/popt The default files POPT reads to obtain aliased options. As these files can specify commands which invoke other applications, MOC will refuse to start if it is not owned by root or the current user, or if it is writable by anyone other than its owner. (Also see the \fBMOCP_POPTRC\fP environment variable above.) .LP .TP .B ~/.moc/themes .TQ .B /usr/share/moc/themes Default directories for the theme files. .LP .TP .B /usr/share/moc/decoder_plugins Default directories for the audio decoder plugins. .LP .TP .B mocp_client_log .TQ .B mocp_server_log Client and server log files. These files are created in the directory in which the client and server are started. (Also see the \fB\-D\fP option.) .LP .SH BUGS Command line options that affect the server behaviour (like \fB\-\-sound\-driver\fP) are ignored if the server is already running at the time of executing \fBmocp\fP. The user is not warned about this. .LP .SH HOMEPAGE http://moc.daper.net/ .LP .SH AUTHOR Damian Pietras .br MOC Maintainer(s) moc-2.6.0~svn-r3005/null_out.c0000664000076400000500000000343612414405141015136 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 Damian Pietras * * 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. * */ /* Fake output device - only for testing. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "common.h" #include "audio.h" static struct sound_params params = { 0, 0, 0 }; static int null_open (struct sound_params *sound_params) { params = *sound_params; return 1; } static void null_close () { params.rate = 0; } static int null_play (const char *unused ATTR_UNUSED, const size_t size) { xsleep (size, audio_get_bps ()); return size; } static int null_read_mixer () { return 100; } static void null_set_mixer (int unused ATTR_UNUSED) { } static int null_get_buff_fill () { return 0; } static int null_reset () { return 1; } static int null_init (struct output_driver_caps *caps) { caps->formats = SFMT_S8 | SFMT_S16 | SFMT_LE; caps->min_channels = 1; caps->max_channels = 2; return 1; } static int null_get_rate () { return params.rate; } static void null_toggle_mixer_channel () { } static char *null_get_mixer_channel_name () { return xstrdup ("FakeMixer"); } void null_funcs (struct hw_funcs *funcs) { funcs->init = null_init; funcs->open = null_open; funcs->close = null_close; funcs->play = null_play; funcs->read_mixer = null_read_mixer; funcs->set_mixer = null_set_mixer; funcs->get_buff_fill = null_get_buff_fill; funcs->reset = null_reset; funcs->get_rate = null_get_rate; funcs->toggle_mixer_channel = null_toggle_mixer_channel; funcs->get_mixer_channel_name = null_get_mixer_channel_name; } moc-2.6.0~svn-r3005/null_out.h0000664000076400000500000000023612212775655015156 0ustar riesebiesrc#ifndef NULL_OUT_H #define NULL_OUT_H #ifdef __cplusplus extern "C" { #endif void null_funcs (struct hw_funcs *funcs); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/options.c0000664000076400000500000010156513224101463014772 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "files.h" #include "log.h" #include "options.h" #include "lists.h" #define OPTIONS_MAX 181 #define OPTION_NAME_MAX 32 typedef int options_t_check (int, ...); union option_value { char *str; int num; bool boolean; lists_t_strs *list; }; struct option { char name[OPTION_NAME_MAX]; enum option_type type; union option_value value; int ignore_in_config; int set_in_config; unsigned int hash; options_t_check *check; int count; void *constraints; }; static struct option options[OPTIONS_MAX]; static int options_num = 0; /* Returns the str's hash using djb2 algorithm. */ static unsigned int hash (const char * str) { unsigned int hash = 5381; while (*str) hash = ((hash << 5) + hash) + tolower(*(str++)); return hash; } /* Return an index to an option in the options hashtable. * If there is no such option return -1. */ static int find_option (const char *name, enum option_type type) { unsigned int h = hash (name), i, init_pos = h % OPTIONS_MAX; for (i = init_pos; i < OPTIONS_MAX; i += 1) { if (options[i].type == OPTION_FREE) return -1; if (h == options[i].hash && type & options[i].type) { if (!strcasecmp (name, options[i].name)) return i; } } for (i = 0; i < init_pos; i += 1) { if (options[i].type == OPTION_FREE) return -1; if (h == options[i].hash && type & options[i].type) { if (!strcasecmp (name, options[i].name)) return i; } } return -1; } /* Return an index of a free slot in the options hashtable. * If there is no such slot return -1. */ static int find_free (unsigned int h) { unsigned int i; assert (options_num < OPTIONS_MAX); h %= OPTIONS_MAX; for (i = h; i < OPTIONS_MAX; i += 1) { if (options[i].type == OPTION_FREE) return i; } for (i = 0; i < h; i += 1) { if (options[i].type == OPTION_FREE) return i; } return -1; } /* Check that a value falls within the specified range(s). */ static int check_range (int opt, ...) { int rc, ix, int_val; char *str_val; va_list va; assert (opt != -1); assert (options[opt].count % 2 == 0); assert (options[opt].type & (OPTION_INT | OPTION_STR | OPTION_LIST)); rc = 0; va_start (va, opt); switch (options[opt].type) { case OPTION_INT: int_val = va_arg (va, int); for (ix = 0; ix < options[opt].count; ix += 2) { if (int_val >= ((int *) options[opt].constraints)[ix] && int_val <= ((int *) options[opt].constraints)[ix + 1]) { rc = 1; break; } } break; case OPTION_STR: case OPTION_LIST: str_val = va_arg (va, char *); for (ix = 0; ix < options[opt].count; ix += 2) { if (strcasecmp (str_val, (((char **) options[opt].constraints)[ix])) >= 0 && strcasecmp (str_val, (((char **) options[opt].constraints)[ix + 1])) <= 0) { rc = 1; break; } } break; case OPTION_BOOL: case OPTION_SYMB: case OPTION_ANY: case OPTION_FREE: break; } va_end (va); return rc; } /* Check that a value is one of the specified values. */ static int check_discrete (int opt, ...) { int rc, ix, int_val; char *str_val; va_list va; assert (opt != -1); assert (options[opt].type & (OPTION_INT | OPTION_SYMB | OPTION_LIST)); rc = 0; va_start (va, opt); switch (options[opt].type) { case OPTION_INT: int_val = va_arg (va, int); for (ix = 0; ix < options[opt].count; ix += 1) { if (int_val == ((int *) options[opt].constraints)[ix]) { rc = 1; break; } } break; case OPTION_SYMB: case OPTION_LIST: str_val = va_arg (va, char *); for (ix = 0; ix < options[opt].count; ix += 1) { if (!strcasecmp(str_val, (((char **) options[opt].constraints)[ix]))) { rc = 1; break; } } break; case OPTION_BOOL: case OPTION_STR: case OPTION_ANY: case OPTION_FREE: break; } va_end (va); return rc; } /* Check that a string length falls within the specified range(s). */ static int check_length (int opt, ...) { int rc, ix, str_len; va_list va; assert (opt != -1); assert (options[opt].count % 2 == 0); assert (options[opt].type & (OPTION_STR | OPTION_LIST)); rc = 0; va_start (va, opt); str_len = strlen (va_arg (va, char *)); for (ix = 0; ix < options[opt].count; ix += 2) { if (str_len >= ((int *) options[opt].constraints)[ix] && str_len <= ((int *) options[opt].constraints)[ix + 1]) { rc = 1; break; } } va_end (va); return rc; } /* Check that a string has a function-like syntax. */ static int check_function (int opt, ...) { int rc; const char *str; const char regex[] = "^[a-z0-9/-]+\\([^,) ]*(,[^,) ]*)*\\)$"; static regex_t *preg = NULL; va_list va; assert (opt != -1); assert (options[opt].count == 0); assert (options[opt].type & (OPTION_STR | OPTION_LIST)); if (preg == NULL) { preg = (regex_t *)xmalloc (sizeof (regex_t)); rc = regcomp (preg, regex, REG_EXTENDED | REG_ICASE | REG_NOSUB); assert (rc == 0); } va_start (va, opt); str = va_arg (va, const char *); rc = regexec (preg, str, 0, NULL, 0); va_end (va); return (rc == 0) ? 1 : 0; } /* Always pass a value as valid. */ static int check_true (int unused ATTR_UNUSED, ...) { return 1; } /* Initializes a position on the options table. This is intended to be used at * initialization to make a table of valid options and its default values. */ static int init_option (const char *name, enum option_type type) { unsigned int h=hash(name); int pos=find_free(h); assert (strlen(name) < OPTION_NAME_MAX); assert (is_valid_symbol (name)); assert(pos>=0); strcpy (options[pos].name, name); options[pos].hash=h; options[pos].type = type; options[pos].ignore_in_config = 0; options[pos].set_in_config = 0; options[pos].check = check_true; options[pos].count = 0; options[pos].constraints = NULL; options_num++; return pos; } /* Add an integer option to the options table. This is intended to be used at * initialization to make a table of valid options and their default values. */ static void add_int (const char *name, const int value, options_t_check *check, const int count, ...) { int ix, pos; va_list va; pos = init_option (name, OPTION_INT); options[pos].value.num = value; options[pos].check = check; options[pos].count = count; if (count > 0) { options[pos].constraints = xcalloc (count, sizeof (int)); va_start (va, count); for (ix = 0; ix < count; ix += 1) ((int *) options[pos].constraints)[ix] = va_arg (va, int); va_end (va); } } /* Add a boolean option to the options table. This is intended to be used at * initialization to make a table of valid options and their default values. */ static void add_bool (const char *name, const bool value) { int pos; pos = init_option (name, OPTION_BOOL); options[pos].value.boolean = value; } /* Add a string option to the options table. This is intended to be used at * initialization to make a table of valid options and their default values. */ static void add_str (const char *name, const char *value, options_t_check *check, const int count, ...) { int ix, pos; va_list va; pos = init_option (name, OPTION_STR); options[pos].value.str = xstrdup (value); options[pos].check = check; options[pos].count = count; if (count > 0) { va_start (va, count); if (check == check_length) { options[pos].constraints = xcalloc (count, sizeof (int)); for (ix = 0; ix < count; ix += 1) ((int *) options[pos].constraints)[ix] = va_arg (va, int); } else { options[pos].constraints = xcalloc (count, sizeof (char *)); for (ix = 0; ix < count; ix += 1) ((char **) options[pos].constraints)[ix] = xstrdup (va_arg (va, char *)); } va_end (va); } } /* Add a symbol option to the options table. This is intended to be used at * initialization to make a table of valid options and their default values. */ static void add_symb (const char *name, const char *value, const int count, ...) { int ix, pos; va_list va; assert (name != NULL); assert (value != NULL); assert (count > 0); pos = init_option (name, OPTION_SYMB); options[pos].value.str = NULL; options[pos].check = check_discrete; options[pos].count = count; va_start (va, count); options[pos].constraints = xcalloc (count, sizeof (char *)); for (ix = 0; ix < count; ix += 1) { char *val = va_arg (va, char *); if (!is_valid_symbol (val)) fatal ("Invalid symbol in '%s' constraint list!", name); ((char **) options[pos].constraints)[ix] = xstrdup (val); if (!strcasecmp (val, value)) options[pos].value.str = ((char **) options[pos].constraints)[ix]; } if (!options[pos].value.str) fatal ("Invalid default value symbol in '%s'!", name); va_end (va); } /* Add a list option to the options table. This is intended to be used at * initialization to make a table of valid options and their default values. */ static void add_list (const char *name, const char *value, options_t_check *check, const int count, ...) { int ix, pos; va_list va; pos = init_option (name, OPTION_LIST); options[pos].value.list = lists_strs_new (8); if (value) lists_strs_split (options[pos].value.list, value, ":"); options[pos].check = check; options[pos].count = count; if (count > 0) { va_start (va, count); if (check == check_length) { options[pos].constraints = xcalloc (count, sizeof (int)); for (ix = 0; ix < count; ix += 1) ((int *) options[pos].constraints)[ix] = va_arg (va, int); } else { options[pos].constraints = xcalloc (count, sizeof (char *)); for (ix = 0; ix < count; ix += 1) ((char **) options[pos].constraints)[ix] = xstrdup (va_arg (va, char *)); } va_end (va); } } /* Set an integer option to the value. */ void options_set_int (const char *name, const int value) { int i = find_option (name, OPTION_INT); if (i == -1) fatal ("Tried to set wrong option '%s'!", name); options[i].value.num = value; } /* Set a boolean option to the value. */ void options_set_bool (const char *name, const bool value) { int i = find_option (name, OPTION_BOOL); if (i == -1) fatal ("Tried to set wrong option '%s'!", name); options[i].value.boolean = value; } /* Set a symbol option to the value. */ void options_set_symb (const char *name, const char *value) { int opt, ix; opt = find_option (name, OPTION_SYMB); if (opt == -1) fatal ("Tried to set wrong option '%s'!", name); options[opt].value.str = NULL; for (ix = 0; ix < options[opt].count; ix += 1) { if (!strcasecmp(value, (((char **) options[opt].constraints)[ix]))) options[opt].value.str = ((char **) options[opt].constraints)[ix]; } if (!options[opt].value.str) fatal ("Tried to set '%s' to unknown symbol '%s'!", name, value); } /* Set a string option to the value. The string is duplicated. */ void options_set_str (const char *name, const char *value) { int opt = find_option (name, OPTION_STR); if (opt == -1) fatal ("Tried to set wrong option '%s'!", name); if (options[opt].value.str) free (options[opt].value.str); options[opt].value.str = xstrdup (value); } /* Set list option values to the colon separated value. */ void options_set_list (const char *name, const char *value, bool append) { int opt; opt = find_option (name, OPTION_LIST); if (opt == -1) fatal ("Tried to set wrong option '%s'!", name); if (!append && !lists_strs_empty (options[opt].value.list)) lists_strs_clear (options[opt].value.list); lists_strs_split (options[opt].value.list, value, ":"); } /* Given a type, a name and a value, set that option's value. * Return false on error. */ bool options_set_pair (const char *name, const char *value, bool append) { int num; char *end; bool val; switch (options_get_type (name)) { case OPTION_INT: num = strtol (value, &end, 10); if (*end) return false; if (!options_check_int (name, num)) return false; options_set_int (name, num); break; case OPTION_BOOL: if (!strcasecmp (value, "yes")) val = true; else if (!strcasecmp (value, "no")) val = false; else return false; options_set_bool (name, val); break; case OPTION_STR: if (!options_check_str (name, value)) return false; options_set_str (name, value); break; case OPTION_SYMB: if (!options_check_symb (name, value)) return false; options_set_symb (name, value); break; case OPTION_LIST: if (!options_check_list (name, value)) return false; options_set_list (name, value, append); break; case OPTION_FREE: case OPTION_ANY: return false; } return true; } void options_ignore_config (const char *name) { int opt = find_option (name, OPTION_ANY); if (opt == -1) fatal ("Tried to set wrong option '%s'!", name); options[opt].ignore_in_config = 1; } #define CHECK_DISCRETE(c) check_discrete, (c) #define CHECK_RANGE(c) check_range, (2 * (c)) #define CHECK_LENGTH(c) check_length, (2 * (c)) #define CHECK_SYMBOL(c) (c) #define CHECK_FUNCTION check_function, 0 #define CHECK_NONE check_true, 0 /* Make a table of options and its default values. */ void options_init () { memset (options, 0, sizeof(options)); add_bool ("ReadTags", true); add_str ("MusicDir", NULL, CHECK_NONE); add_bool ("StartInMusicDir", false); add_int ("CircularLogSize", 0, CHECK_RANGE(1), 0, INT_MAX); add_symb ("Sort", "FileName", CHECK_SYMBOL(1), "FileName"); add_bool ("ShowStreamErrors", false); add_bool ("MP3IgnoreCRCErrors", true); add_bool ("Repeat", false); add_bool ("Shuffle", false); add_bool ("AutoNext", true); add_str ("FormatString", "%(n:%n :)%(a:%a - :)%(t:%t:)%(A: \\(%A\\):)", CHECK_NONE); add_int ("InputBuffer", 512, CHECK_RANGE(1), 32, INT_MAX); add_int ("OutputBuffer", 512, CHECK_RANGE(1), 128, INT_MAX); add_int ("Prebuffering", 64, CHECK_RANGE(1), 0, INT_MAX); add_str ("HTTPProxy", NULL, CHECK_NONE); #ifdef OPENBSD add_list ("SoundDriver", "SNDIO:JACK:OSS", CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null"); #else add_list ("SoundDriver", "Jack:ALSA:OSS", CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null"); #endif add_str ("JackClientName", "moc", CHECK_NONE); add_bool ("JackStartServer", false); add_str ("JackOutLeft", "system:playback_1", CHECK_NONE); add_str ("JackOutRight", "system:playback_2", CHECK_NONE); add_str ("OSSDevice", "/dev/dsp", CHECK_NONE); add_str ("OSSMixerDevice", "/dev/mixer", CHECK_NONE); add_symb ("OSSMixerChannel1", "pcm", CHECK_SYMBOL(3), "pcm", "master", "speaker"); add_symb ("OSSMixerChannel2", "master", CHECK_SYMBOL(3), "pcm", "master", "speaker"); add_str ("ALSADevice", "default", CHECK_NONE); add_str ("ALSAMixer1", "PCM", CHECK_NONE); add_str ("ALSAMixer2", "Master", CHECK_NONE); add_bool ("ALSAStutterDefeat", false); add_bool ("Softmixer_SaveState", true); add_bool ("Equalizer_SaveState", true); add_bool ("ShowHiddenFiles", false); add_bool ("HideFileExtension", false); add_bool ("ShowFormat", true); add_symb ("ShowTime", "IfAvailable", CHECK_SYMBOL(3), "yes", "no", "IfAvailable"); add_bool ("ShowTimePercent", false); add_list ("ScreenTerms", "screen:screen-w:vt100", CHECK_NONE); add_list ("XTerms", "xterm:" "xterm-colour:xterm-color:" "xterm-256colour:xterm-256color:" "rxvt:rxvt-unicode:" "rxvt-unicode-256colour:rxvt-unicode-256color:" "eterm", CHECK_NONE); add_str ("Theme", NULL, CHECK_NONE); add_str ("XTermTheme", NULL, CHECK_NONE); add_str ("ForceTheme", NULL, CHECK_NONE); /* Used when -T is set */ add_bool ("AutoLoadLyrics", true); add_str ("MOCDir", "~/.moc", CHECK_NONE); add_bool ("UseMMap", false); add_bool ("UseMimeMagic", false); add_str ("ID3v1TagsEncoding", "WINDOWS-1250", CHECK_NONE); add_bool ("UseRCC", true); add_bool ("UseRCCForFilesystem", true); add_bool ("EnforceTagsEncoding", false); add_bool ("FileNamesIconv", false); add_bool ("NonUTFXterm", false); add_bool ("Precache", true); add_bool ("SavePlaylist", true); add_bool ("SyncPlaylist", true); add_str ("Keymap", NULL, CHECK_NONE); add_bool ("ASCIILines", false); add_str ("FastDir1", NULL, CHECK_NONE); add_str ("FastDir2", NULL, CHECK_NONE); add_str ("FastDir3", NULL, CHECK_NONE); add_str ("FastDir4", NULL, CHECK_NONE); add_str ("FastDir5", NULL, CHECK_NONE); add_str ("FastDir6", NULL, CHECK_NONE); add_str ("FastDir7", NULL, CHECK_NONE); add_str ("FastDir8", NULL, CHECK_NONE); add_str ("FastDir9", NULL, CHECK_NONE); add_str ("FastDir10", NULL, CHECK_NONE); add_int ("SeekTime", 1, CHECK_RANGE(1), 1, INT_MAX); add_int ("SilentSeekTime", 5, CHECK_RANGE(1), 1, INT_MAX); add_list ("PreferredDecoders", "aac(aac,ffmpeg):m4a(ffmpeg):" "mpc(musepack,*,ffmpeg):mpc8(musepack,*,ffmpeg):" "sid(sidplay2):mus(sidplay2):" "wav(sndfile,*,ffmpeg):" "wv(wavpack,*,ffmpeg):" "audio/aac(aac):audio/aacp(aac):audio/m4a(ffmpeg):" "audio/wav(sndfile,*):" "ogg(vorbis,*,ffmpeg):oga(vorbis,*,ffmpeg):ogv(ffmpeg):" "application/ogg(vorbis):audio/ogg(vorbis):" "flac(flac,*,ffmpeg):" "opus(ffmpeg):" "spx(speex)", CHECK_FUNCTION); add_symb ("ResampleMethod", "Linear", CHECK_SYMBOL(5), "SincBestQuality", "SincMediumQuality", "SincFastest", "ZeroOrderHold", "Linear"); add_int ("ForceSampleRate", 0, CHECK_RANGE(1), 0, 500000); add_bool ("Allow24bitOutput", false); add_bool ("UseRealtimePriority", false); add_int ("TagsCacheSize", 256, CHECK_RANGE(1), 0, INT_MAX); add_bool ("PlaylistNumbering", true); add_list ("Layout1", "directory(0,0,50%,100%):playlist(50%,0,FILL,100%)", CHECK_FUNCTION); add_list ("Layout2", "directory(0,0,100%,100%):playlist(0,0,100%,100%)", CHECK_FUNCTION); add_list ("Layout3", NULL, CHECK_FUNCTION); add_bool ("FollowPlayedFile", true); add_bool ("CanStartInPlaylist", true); add_str ("ExecCommand1", NULL, CHECK_NONE); add_str ("ExecCommand2", NULL, CHECK_NONE); add_str ("ExecCommand3", NULL, CHECK_NONE); add_str ("ExecCommand4", NULL, CHECK_NONE); add_str ("ExecCommand5", NULL, CHECK_NONE); add_str ("ExecCommand6", NULL, CHECK_NONE); add_str ("ExecCommand7", NULL, CHECK_NONE); add_str ("ExecCommand8", NULL, CHECK_NONE); add_str ("ExecCommand9", NULL, CHECK_NONE); add_str ("ExecCommand10", NULL, CHECK_NONE); add_bool ("UseCursorSelection", false); add_bool ("SetXtermTitle", true); add_bool ("SetScreenTitle", true); add_bool ("PlaylistFullPaths", true); add_str ("BlockDecorators", "`\"'", CHECK_LENGTH(1), 3, 3); add_int ("MessageLingerTime", 3, CHECK_RANGE(1), 0, INT_MAX); add_bool ("PrefixQueuedMessages", true); add_str ("ErrorMessagesQueued", "!", CHECK_NONE); add_bool ("ModPlug_Oversampling", true); add_bool ("ModPlug_NoiseReduction", true); add_bool ("ModPlug_Reverb", false); add_bool ("ModPlug_MegaBass", false); add_bool ("ModPlug_Surround", false); add_symb ("ModPlug_ResamplingMode", "FIR", CHECK_SYMBOL(4), "FIR", "SPLINE", "LINEAR", "NEAREST"); add_int ("ModPlug_Channels", 2, CHECK_DISCRETE(2), 1, 2); add_int ("ModPlug_Bits", 16, CHECK_DISCRETE(3), 8, 16, 32); add_int ("ModPlug_Frequency", 44100, CHECK_DISCRETE(4), 11025, 22050, 44100, 48000); add_int ("ModPlug_ReverbDepth", 0, CHECK_RANGE(1), 0, 100); add_int ("ModPlug_ReverbDelay", 0, CHECK_RANGE(1), 0, INT_MAX); add_int ("ModPlug_BassAmount", 0, CHECK_RANGE(1), 0, 100); add_int ("ModPlug_BassRange", 10, CHECK_RANGE(1), 10, 100); add_int ("ModPlug_SurroundDepth", 0, CHECK_RANGE(1), 0, 100); add_int ("ModPlug_SurroundDelay", 0, CHECK_RANGE(1), 0, INT_MAX); add_int ("ModPlug_LoopCount", 0, CHECK_RANGE(1), -1, INT_MAX); add_int ("TiMidity_Rate", 44100, CHECK_RANGE(1), 8000, 48000); // not sure about the limits... I like 44100 add_int ("TiMidity_Bits", 16, CHECK_DISCRETE(2), 8, 16); add_int ("TiMidity_Channels", 2, CHECK_DISCRETE(2), 1, 2); add_int ("TiMidity_Volume", 100, CHECK_RANGE(1), 0, 800); add_str ("TiMidity_Config", NULL, CHECK_NONE); add_int ("SidPlay2_DefaultSongLength", 180, CHECK_RANGE(1), 0, INT_MAX); add_int ("SidPlay2_MinimumSongLength", 0, CHECK_RANGE(1), 0, INT_MAX); add_str ("SidPlay2_Database", NULL, CHECK_NONE); add_int ("SidPlay2_Frequency", 44100, CHECK_RANGE(1), 4000, 48000); add_int ("SidPlay2_Bits", 16, CHECK_DISCRETE(2), 8, 16); add_int ("SidPlay2_Optimisation", 0, CHECK_RANGE(1), 0, 2); add_symb ("SidPlay2_PlayMode", "M", CHECK_SYMBOL(4), "M", "S", "L", "R"); add_bool ("SidPlay2_StartAtStart", true); add_bool ("SidPlay2_PlaySubTunes", true); add_str ("OnSongChange", NULL, CHECK_NONE); add_bool ("RepeatSongChange", false); add_str ("OnServerStart", NULL, CHECK_NONE); add_str ("OnServerStop", NULL, CHECK_NONE); add_str ("OnStop", NULL, CHECK_NONE); add_bool ("QueueNextSongReturn", false); } /* Return 1 if a parameter to an integer option is valid. */ int options_check_int (const char *name, const int val) { int opt; opt = find_option (name, OPTION_INT); if (opt == -1) return 0; return options[opt].check (opt, val); } /* Return 1 if a parameter to a boolean option is valid. This may seem * pointless but it provides a consistant interface, ensures the existence * of the option and checks the value where true booleans are emulated with * other types. */ int options_check_bool (const char *name, const bool val) { int opt, result = 0; opt = find_option (name, OPTION_BOOL); if (opt == -1) return 0; if (val == true || val == false) result = 1; return result; } /* Return 1 if a parameter to a string option is valid. */ int options_check_str (const char *name, const char *val) { int opt; opt = find_option (name, OPTION_STR); if (opt == -1) return 0; return options[opt].check (opt, val); } /* Return 1 if a parameter to a symbol option is valid. */ int options_check_symb (const char *name, const char *val) { int opt; opt = find_option (name, OPTION_SYMB); if (opt == -1) return 0; return check_discrete (opt, val); } /* Return 1 if a parameter to a list option is valid. */ int options_check_list (const char *name, const char *val) { int opt, size, ix, result; lists_t_strs *list; assert (name); assert (val); opt = find_option (name, OPTION_LIST); if (opt == -1) return 0; list = lists_strs_new (8); size = lists_strs_split (list, val, ":"); result = 1; for (ix = 0; ix < size; ix += 1) { if (!options[opt].check (opt, lists_strs_at (list, ix))) { result = 0; break; } } lists_strs_free (list); return result; } /* Return 1 if the named option was defaulted. */ int options_was_defaulted (const char *name) { int opt, result = 0; assert (name); opt = find_option (name, OPTION_ANY); if (opt == -1) return 0; if (!options[opt].set_in_config && !options[opt].ignore_in_config) result = 1; return result; } /* Find and substitute variables enclosed by '${...}'. Variables are * substituted first from the environment then, if not found, from * the configuration options. Strings of the form '$${' are reduced to * '${' and not substituted. The result is returned as a new string. */ static char *substitute_variable (const char *name_in, const char *value_in) { size_t len; char *dollar, *result, *ptr, *name, *value, *dflt, *end; static const char accept[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; lists_t_strs *strs; result = xstrdup (value_in); ptr = result; strs = lists_strs_new (5); dollar = strstr (result, "${"); while (dollar) { /* Escape "$${". */ if (dollar > ptr && dollar[-1] == '$') { dollar[-1] = 0x00; lists_strs_append (strs, ptr); ptr = dollar; dollar = strstr (&dollar[2], "${"); continue; } /* Copy up to this point verbatim. */ dollar[0] = 0x00; lists_strs_append (strs, ptr); /* Find where the substitution variable name ends. */ name = &dollar[2]; len = strspn (name, accept); if (len == 0) fatal ("Error in config file option '%s':\n" " substitution variable name is missing!", name_in); /* Find default substitution or closing brace. */ dflt = NULL; if (name[len] == '}') { end = &name[len]; end[0] = 0x00; } else if (strncmp (&name[len], ":-", 2) == 0) { name[len] = 0x00; dflt = &name[len + 2]; end = strchr (dflt, '}'); if (end == NULL) fatal ("Error in config file option '%s': " "unterminated '${%s:-'!", name_in, name); end[0] = 0x00; } else if (name[len] == 0x00) { fatal ("Error in config file option '%s': " "unterminated '${'!", name_in); } else { fatal ("Error in config file option '%s':\n" " expecting ':-' or '}' found '%c'!", name_in, name[len]); } /* Fetch environment variable or configuration option value. */ value = xstrdup (getenv (name)); if (value == NULL && find_option (name, OPTION_ANY) != -1) { char buf[16]; lists_t_strs *list; switch (options_get_type (name)) { case OPTION_INT: snprintf (buf, sizeof (buf), "%d", options_get_int (name)); value = xstrdup (buf); break; case OPTION_BOOL: value = xstrdup (options_get_bool (name) ? "yes" : "no"); break; case OPTION_STR: value = xstrdup (options_get_str (name)); break; case OPTION_SYMB: value = xstrdup (options_get_symb (name)); break; case OPTION_LIST: list = options_get_list (name); if (!lists_strs_empty (list)) { value = lists_strs_fmt (list, "%s:"); value[strlen (value) - 1] = 0x00; } break; case OPTION_FREE: case OPTION_ANY: break; } } if (value && value[0]) lists_strs_append (strs, value); else if (dflt) lists_strs_append (strs, dflt); else fatal ("Error in config file option '%s':\n" " substitution variable '%s' not set or null!", name_in, &dollar[2]); free (value); /* Go look for another substitution. */ ptr = &end[1]; dollar = strstr (ptr, "${"); } /* If anything changed copy segments to result. */ if (!lists_strs_empty (strs)) { lists_strs_append (strs, ptr); free (result); result = lists_strs_cat (strs); } lists_strs_free (strs); return result; } /* Set an option read from the configuration file. Return false on error. */ static bool set_option (const char *name, const char *value_in, bool append) { int i; char *value; i = find_option (name, OPTION_ANY); if (i == -1) { fprintf (stderr, "Wrong option name: '%s'.", name); return false; } if (options[i].ignore_in_config) return true; if (append && options[i].type != OPTION_LIST) { fprintf (stderr, "Only list valued options can be appended to ('%s').", name); return false; } if (!append && options[i].set_in_config) { fprintf (stderr, "Tried to set an option that has been already " "set in the config file ('%s').", name); return false; } options[i].set_in_config = 1; /* Substitute environmental variables. */ value = substitute_variable (name, value_in); if (!options_set_pair (name, value, append)) return false; free (value); return true; } /* Check if values of options make sense. This only checks options that can't * be checked without parsing the whole file. */ static void sanity_check () { if (options_get_int ("Prebuffering") > options_get_int ("InputBuffer")) fatal ("Prebuffering is set to a value greater than InputBuffer!"); } /* Parse the configuration file. */ void options_parse (const char *config_file) { int ch; int comm = 0; /* comment? */ int eq = 0; /* equal character appeared? */ int quote = 0; /* are we in quotes? */ int esc = 0; bool plus = false; /* plus character appeared? */ bool append = false; /* += (list append) appeared */ bool sp = false; /* first post-name space detected */ char opt_name[30]; char opt_value[512]; int line = 1; int name_pos = 0; int value_pos = 0; FILE *file; if (!is_secure (config_file)) fatal ("Configuration file is not secure: %s", config_file); if (!(file = fopen(config_file, "r"))) { log_errno ("Can't open config file", errno); return; } while ((ch = getc(file)) != EOF) { /* Skip comment */ if (comm && ch != '\n') continue; /* Check for "+=" (list append) */ if (ch != '=' && plus) fatal ("Error in config file: stray '+' on line %d!", line); /* Interpret parameter */ if (ch == '\n') { comm = 0; opt_name[name_pos] = 0; opt_value[value_pos] = 0; if (name_pos) { if (value_pos == 0 && strncasecmp (opt_name, "Layout", 6)) fatal ("Error in config file: " "missing option value on line %d!", line); if (!set_option (opt_name, opt_value, append)) fatal ("Error in config file on line %d!", line); } name_pos = 0; value_pos = 0; eq = 0; quote = 0; esc = 0; append = false; sp = false; line++; } /* Turn on comment */ else if (ch == '#' && !quote) comm = 1; /* Turn on quote */ else if (!quote && !esc && (ch == '"')) quote = 1; /* Turn off quote */ else if (!esc && quote && ch == '"') quote = 0; else if (!esc && !eq && ch == '+') plus = true; else if (ch == '=' && !quote) { if (eq) fatal ("Error in config file: stray '=' on line %d!", line); if (name_pos == 0) fatal ("Error in config file: " "missing option name on line %d!", line); append = plus; plus = false; eq = 1; } /* Turn on escape */ else if (ch == '\\' && !esc) esc = 1; /* Embedded blank detection */ else if (!eq && name_pos && isblank(ch)) sp = true; else if (!eq && sp && !isblank(ch)) fatal ("Error in config file: " "embedded blank in option name on line %d!", line); /* Add char to parameter value */ else if ((!isblank(ch) || quote) && eq) { if (esc && ch != '"') { if (sizeof(opt_value) == value_pos) fatal ("Error in config file: " "option value on line %d is too long!", line); opt_value[value_pos++] = '\\'; } if (sizeof(opt_value) == value_pos) fatal ("Error in config file: " "option value on line %d is too long!", line); opt_value[value_pos++] = ch; esc = 0; } /* Add char to parameter name */ else if (!isblank(ch) || quote) { if (sizeof(opt_name) == name_pos) fatal ("Error in config file: " "option name on line %d is too long!", line); opt_name[name_pos++] = ch; esc = 0; } } if (name_pos || value_pos) fatal ("Parse error at the end of the config file (need end of " "line?)!"); sanity_check (); fclose (file); } void options_free () { int i, ix; for (i = 0; i < options_num; i++) { if (options[i].type == OPTION_STR && options[i].value.str) { free (options[i].value.str); options[i].value.str = NULL; } else if (options[i].type == OPTION_LIST) { lists_strs_free (options[i].value.list); options[i].value.list = NULL; for (ix = 0; ix < options[i].count; ix += 1) free (((char **) options[i].constraints)[ix]); } else if (options[i].type == OPTION_SYMB) options[i].value.str = NULL; if (options[i].type & (OPTION_STR | OPTION_SYMB)) { if (options[i].check != check_length) { for (ix = 0; ix < options[i].count; ix += 1) free (((char **) options[i].constraints)[ix]); } } options[i].check = check_true; options[i].count = 0; if (options[i].constraints) free (options[i].constraints); options[i].constraints = NULL; } } int options_get_int (const char *name) { int i = find_option (name, OPTION_INT); if (i == -1) fatal ("Tried to get wrong option '%s'!", name); return options[i].value.num; } bool options_get_bool (const char *name) { int i = find_option (name, OPTION_BOOL); if (i == -1) fatal ("Tried to get wrong option '%s'!", name); return options[i].value.boolean; } char *options_get_str (const char *name) { int i = find_option (name, OPTION_STR); if (i == -1) fatal ("Tried to get wrong option '%s'!", name); return options[i].value.str; } char *options_get_symb (const char *name) { int i = find_option (name, OPTION_SYMB); if (i == -1) fatal ("Tried to get wrong option '%s'!", name); return options[i].value.str; } lists_t_strs *options_get_list (const char *name) { int i = find_option (name, OPTION_LIST); if (i == -1) fatal ("Tried to get wrong option '%s'!", name); return options[i].value.list; } enum option_type options_get_type (const char *name) { int i = find_option (name, OPTION_ANY); if (i == -1) return OPTION_FREE; return options[i].type; } moc-2.6.0~svn-r3005/options.h0000664000076400000500000000261613012007156014773 0ustar riesebiesrc#ifndef OPTIONS_H #define OPTIONS_H #include "lists.h" #ifdef __cplusplus extern "C" { #endif enum option_type { OPTION_FREE = 0, OPTION_INT = 1, OPTION_BOOL = 2, OPTION_STR = 4, OPTION_SYMB = 8, OPTION_LIST = 16, OPTION_ANY = 255 }; int options_get_int (const char *name); bool options_get_bool (const char *name); char *options_get_str (const char *name); char *options_get_symb (const char *name); lists_t_strs *options_get_list (const char *name); void options_set_int (const char *name, const int value); void options_set_bool (const char *name, const bool value); void options_set_str (const char *name, const char *value); void options_set_symb (const char *name, const char *value); void options_set_list (const char *name, const char *value, bool append); bool options_set_pair (const char *name, const char *value, bool append); void options_init (); void options_parse (const char *config_file); void options_free (); void options_ignore_config (const char *name); int options_check_str (const char *name, const char *val); int options_check_symb (const char *name, const char *val); int options_check_int (const char *name, const int val); int options_check_bool (const char *name, const bool val); int options_check_list (const char *name, const char *val); int options_was_defaulted (const char *name); enum option_type options_get_type (const char *name); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/oss.c0000664000076400000500000002424513012456773014116 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2003 - 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SOUNDCARD_H # include #else # include #endif #include "common.h" #include "server.h" #include "audio.h" #include "log.h" #include "options.h" #if OSS_VERSION >= 0x40000 || SOUND_VERSION >= 0x40000 #define OSSv4_MIXER #else #define OSSv3_MIXER #endif static bool started = false; static int volatile dsp_fd = -1; #ifdef OSSv3_MIXER static int mixer_fd = -1; static int mixer_channel1 = -1; static int mixer_channel2 = -1; static int mixer_channel_current; #endif static struct sound_params params = { 0, 0, 0 }; static const struct { const char *name; const int num; } mixer_channels[] = { { "pcm", SOUND_MIXER_PCM }, { "master", SOUND_MIXER_VOLUME }, { "speaker", SOUND_MIXER_SPEAKER } }; #define MIXER_CHANNELS_NUM (ARRAY_SIZE(mixer_channels)) static int open_dev () { if ((dsp_fd = open (options_get_str ("OSSDevice"), O_WRONLY)) == -1) { char *err = xstrerror (errno); error ("Can't open %s: %s", options_get_str ("OSSDevice"), err); free (err); return 0; } logit ("Audio device opened"); return 1; } /* Fill caps with the device capabilities. Return 0 on error. */ static int set_capabilities (struct output_driver_caps *caps) { int format_mask; if (!open_dev ()) { error ("Can't open the device."); return 0; } if (ioctl (dsp_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1) { error_errno ("Can't get supported audio formats", errno); close (dsp_fd); return 0; } caps->formats = 0; if (format_mask & AFMT_S8) caps->formats |= SFMT_S8; if (format_mask & AFMT_U8) caps->formats |= SFMT_U8; if (format_mask & AFMT_S16_LE) caps->formats |= SFMT_S16 | SFMT_LE; if (format_mask & AFMT_S16_BE) caps->formats |= SFMT_S16 | SFMT_BE; #if defined(AFMT_S32_LE) && defined(AFMT_S32_BE) if (format_mask & AFMT_S32_LE) caps->formats |= SFMT_S32 | SFMT_LE; if (format_mask & AFMT_S32_BE) caps->formats |= SFMT_S32 | SFMT_BE; #endif if (!caps->formats) { /* Workaround for vmix which lies that it doesn't support any * format. */ error ("The driver claims that no format known to me is " "supported. I will assume that SFMT_S8 and " "SFMT_S16 (native endian) are supported."); caps->formats = SFMT_S8 | SFMT_S16 | SFMT_NE; } caps->min_channels = caps->max_channels = 1; if (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &caps->min_channels)) { error_errno ("Can't set number of channels", errno); close (dsp_fd); return 0; } close (dsp_fd); if (!open_dev ()) { error ("Can't open the device."); return 0; } if (caps->min_channels != 1) caps->min_channels = 2; caps->max_channels = 2; if (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &caps->max_channels)) { error_errno ("Can't set number of channels", errno); close (dsp_fd); return 0; } if (caps->max_channels != 2) { if (caps->min_channels == 2) { error ("Can't get any supported number of channels."); close (dsp_fd); return 0; } caps->max_channels = 1; } close (dsp_fd); return 1; } /* Get PCM volume. Return -1 on error. */ static int oss_read_mixer () { int vol; if (!started) return -1; #ifdef OSSv3_MIXER if (mixer_fd != -1 && mixer_channel_current != -1) { if (ioctl (mixer_fd, MIXER_READ(mixer_channel_current), &vol) == -1) #else if (dsp_fd != -1) { if (ioctl (dsp_fd, SNDCTL_DSP_GETPLAYVOL, &vol) == -1) #endif { error ("Can't read from mixer"); } else { /* Average between left and right */ return ((vol & 0xFF) + ((vol >> 8) & 0xFF)) / 2; } } return -1; } static int oss_mixer_name_to_channel (const char *name) { size_t ix; for (ix = 0; ix < MIXER_CHANNELS_NUM; ix += 1) { if (!strcasecmp (mixer_channels[ix].name, name)) return ix; } return -1; } static int oss_init (struct output_driver_caps *caps) { #ifdef OSSv3_MIXER /* Open the mixer device */ mixer_fd = open (options_get_str ("OSSMixerDevice"), O_RDWR); if (mixer_fd == -1) { char *err = xstrerror (errno); error ("Can't open mixer device %s: %s", options_get_str ("OSSMixerDevice"), err); free (err); } else { mixer_channel1 = oss_mixer_name_to_channel ( options_get_symb ("OSSMixerChannel1")); mixer_channel2 = oss_mixer_name_to_channel ( options_get_symb ("OSSMixerChannel2")); if (mixer_channel1 == -1) fatal ("Bad first OSS mixer channel!"); if (mixer_channel2 == -1) fatal ("Bad second OSS mixer channel!"); /* test mixer channels */ mixer_channel_current = mixer_channel1; if (oss_read_mixer () == -1) mixer_channel1 = -1; mixer_channel_current = mixer_channel2; if (oss_read_mixer () == -1) mixer_channel2 = -1; if (mixer_channel1 != -1) mixer_channel_current = mixer_channel1; } #endif return set_capabilities (caps); } static void oss_shutdown () { #ifdef OSSv3_MIXER if (mixer_fd != -1) { close (mixer_fd); mixer_fd = -1; } #endif } static void oss_close () { if (dsp_fd != -1) { close (dsp_fd); dsp_fd = -1; logit ("Audio device closed"); } started = false; params.channels = 0; params.rate = 0; params.fmt = 0; } /* Return 0 on error. */ static int oss_set_params () { int req_format; int req_channels; char fmt_name[SFMT_STR_MAX]; /* Set format */ switch (params.fmt & SFMT_MASK_FORMAT) { case SFMT_S8: req_format = AFMT_S8; break; case SFMT_U8: req_format = AFMT_U8; break; case SFMT_S16: if (params.fmt & SFMT_LE) req_format = AFMT_S16_LE; else req_format = AFMT_S16_BE; break; #if defined(AFMT_S32_LE) && defined(AFMT_S32_BE) case SFMT_S32: if (params.fmt & SFMT_LE) req_format = AFMT_S32_LE; else req_format = AFMT_S32_BE; break; #endif default: error ("Format %s is not supported by the device", sfmt_str (params.fmt, fmt_name, sizeof (fmt_name))); return 0; } if (ioctl (dsp_fd, SNDCTL_DSP_SETFMT, &req_format) == -1) { error_errno ("Can't set audio format", errno); oss_close (); return 0; } /* Set number of channels */ req_channels = params.channels; if (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &req_channels) == -1) { char *err = xstrerror (errno); error ("Can't set number of channels to %d: %s", params.channels, err); free (err); oss_close (); return 0; } if (params.channels != req_channels) { error ("Can't set number of channels to %d, " "device doesn't support this value", params.channels); oss_close (); return 0; } /* Set sample rate */ if (ioctl (dsp_fd, SNDCTL_DSP_SPEED, ¶ms.rate) == -1) { char *err = xstrerror (errno); error ("Can't set sampling rate to %d: %s", params.rate, err); free (err); oss_close (); return 0; } logit ("Audio parameters set to: %s, %d channels, %dHz", sfmt_str (params.fmt, fmt_name, sizeof (fmt_name)), params.channels, params.rate); return 1; } /* Return 0 on failure. */ static int oss_open (struct sound_params *sound_params) { params = *sound_params; if (!open_dev ()) return 0; if (!oss_set_params ()) { oss_close (); return 0; } started = true; return 1; } /* Return -1 on error, or number of bytes played when okay. */ static int oss_play (const char *buff, const size_t size) { ssize_t ssize = (ssize_t) size; ssize_t count = 0; if (dsp_fd == -1) { error ("Can't play: audio device isn't opened!"); return -1; } while (count < ssize) { ssize_t rc; rc = write (dsp_fd, buff + count, ssize - count); if (rc == -1) { error_errno ("Error writing pcm sound", errno); return -1; } count += rc; } return count; } /* Set PCM volume */ static void oss_set_mixer (int vol) { #ifdef OSSv3_MIXER if (mixer_fd != -1) #else if (dsp_fd != -1) #endif { vol = CLAMP(0, vol, 100); vol |= vol << 8; #ifdef OSSv3_MIXER if (ioctl (mixer_fd, MIXER_WRITE(mixer_channel_current), &vol) == -1) #else if (ioctl (dsp_fd, SNDCTL_DSP_SETPLAYVOL, &vol) == -1) #endif { error ("Can't set mixer: ioctl() failed"); } } } /* Return number of bytes in device buffer. */ static int oss_get_buff_fill () { audio_buf_info buff_info; if (dsp_fd == -1) return 0; if (ioctl (dsp_fd, SNDCTL_DSP_GETOSPACE, &buff_info) == -1) { error ("SNDCTL_DSP_GETOSPACE failed"); return 0; } return (buff_info.fragstotal * buff_info.fragsize) - buff_info.bytes; } /* Reset device buffer and stop playing immediately. Return 0 on error. */ static int oss_reset () { if (dsp_fd == -1) { logit ("Reset when audio device is not opened"); return 0; } logit ("Resetting audio device"); if (ioctl (dsp_fd, SNDCTL_DSP_RESET, NULL) == -1) error ("Resetting audio device failed"); close (dsp_fd); dsp_fd = -1; if (!open_dev () || !oss_set_params ()) { error ("Failed to open audio device after resetting"); return 0; } return 1; } static void oss_toggle_mixer_channel () { #ifdef OSSv3_MIXER if (mixer_channel_current == mixer_channel1 && mixer_channel2 != -1) mixer_channel_current = mixer_channel2; else if (mixer_channel1 != -1) mixer_channel_current = mixer_channel1; #endif } static char *oss_get_mixer_channel_name () { #ifdef OSSv3_MIXER if (mixer_channel_current == mixer_channel1) return xstrdup (options_get_symb ("OSSMixerChannel1")); return xstrdup (options_get_symb ("OSSMixerChannel2")); #else return xstrdup ("moc"); #endif } static int oss_get_rate () { return params.rate; } void oss_funcs (struct hw_funcs *funcs) { funcs->init = oss_init; funcs->shutdown = oss_shutdown; funcs->open = oss_open; funcs->close = oss_close; funcs->play = oss_play; funcs->read_mixer = oss_read_mixer; funcs->set_mixer = oss_set_mixer; funcs->get_buff_fill = oss_get_buff_fill; funcs->reset = oss_reset; funcs->get_rate = oss_get_rate; funcs->toggle_mixer_channel = oss_toggle_mixer_channel; funcs->get_mixer_channel_name = oss_get_mixer_channel_name; } moc-2.6.0~svn-r3005/oss.h0000664000076400000500000000024711322047704014107 0ustar riesebiesrc#ifndef OSS_H #define OSS_H #include "audio.h" #ifdef __cplusplus extern "C" { #endif void oss_funcs (struct hw_funcs *funcs); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/out_buf.c0000664000076400000500000002422412661770467014762 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004,2005 Damian Pietras * * 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. * */ /* Defining OUT_TEST causes the raw audio samples to be written * to the file 'out_test' in the current directory for debugging. */ /*#define OUT_TEST*/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef OUT_TEST #include #include #include #include #endif /*#define DEBUG*/ #include "common.h" #include "audio.h" #include "log.h" #include "fifo_buf.h" #include "out_buf.h" #include "options.h" struct out_buf { struct fifo_buf *buf; pthread_mutex_t mutex; pthread_t tid; /* Thread id of the reading thread. */ /* Signals. */ pthread_cond_t play_cond; /* Something was written to the buffer. */ pthread_cond_t ready_cond; /* There is some space in the buffer. */ /* Optional callback called when there is some free space in * the buffer. */ out_buf_free_callback *free_callback; /* State flags of the buffer. */ int pause; int exit; /* Exit when the buffer is empty. */ int stop; /* Don't play anything. */ int reset_dev; /* Request to the reading thread to reset the audio device. */ float time; /* Time of played sound. */ int hardware_buf_fill; /* How the sound card buffer is filled. */ int read_thread_waiting; /* Is the read thread waiting for data? */ }; /* Don't play more than this value (in seconds) in one audio_play(). * This prevents locking. */ #define AUDIO_MAX_PLAY 0.1 #define AUDIO_MAX_PLAY_BYTES 32768 #ifdef OUT_TEST static int fd; #endif static void set_realtime_prio () { #ifdef HAVE_SCHED_GET_PRIORITY_MAX int rc; if (options_get_bool("UseRealtimePriority")) { struct sched_param param; param.sched_priority = sched_get_priority_max(SCHED_RR); rc = pthread_setschedparam (pthread_self (), SCHED_RR, ¶m); if (rc != 0) log_errno ("Can't set realtime priority", rc); } #else logit ("No sched_get_priority_max() function: " "realtime priority not used."); #endif } /* Reading thread of the buffer. */ static void *read_thread (void *arg) { struct out_buf *buf = (struct out_buf *)arg; int audio_dev_closed = 0; logit ("entering output buffer thread"); set_realtime_prio (); LOCK (buf->mutex); while (1) { int played = 0; char play_buf[AUDIO_MAX_PLAY_BYTES]; int play_buf_fill; int play_buf_pos = 0; if (buf->reset_dev && !audio_dev_closed) { audio_reset (); buf->reset_dev = 0; } if (buf->stop) fifo_buf_clear (buf->buf); if (buf->free_callback) { /* unlock the mutex to make calls to out_buf functions * possible in the callback */ UNLOCK (buf->mutex); buf->free_callback (); LOCK (buf->mutex); } debug ("sending the signal"); pthread_cond_broadcast (&buf->ready_cond); if ((fifo_buf_get_fill(buf->buf) == 0 || buf->pause || buf->stop) && !buf->exit) { if (buf->pause && !audio_dev_closed) { logit ("Closing the device due to pause"); audio_close (); audio_dev_closed = 1; } debug ("waiting for something in the buffer"); buf->read_thread_waiting = 1; pthread_cond_wait (&buf->play_cond, &buf->mutex); debug ("something appeared in the buffer"); } buf->read_thread_waiting = 0; if (audio_dev_closed && !buf->pause) { logit ("Opening the device again after pause"); if (!audio_open(NULL)) { logit ("Can't reopen the device! sleeping..."); xsleep (1, 1); /* there is no way to exit :( */ } else audio_dev_closed = 0; } if (fifo_buf_get_fill(buf->buf) == 0) { if (buf->exit) { logit ("exit"); break; } logit ("buffer empty"); continue; } if (buf->pause) { logit ("paused"); continue; } if (buf->stop) { logit ("stopped"); continue; } if (!audio_dev_closed) { int audio_bpf; size_t play_buf_frames; audio_bpf = audio_get_bpf(); play_buf_frames = MIN(audio_get_bps() * AUDIO_MAX_PLAY, AUDIO_MAX_PLAY_BYTES) / audio_bpf; play_buf_fill = fifo_buf_get(buf->buf, play_buf, play_buf_frames * audio_bpf); UNLOCK (buf->mutex); debug ("playing %d bytes", play_buf_fill); while (play_buf_pos < play_buf_fill) { played = audio_send_pcm ( play_buf + play_buf_pos, play_buf_fill - play_buf_pos); #ifdef OUT_TEST write (fd, play_buf + play_buf_pos, played); #endif play_buf_pos += played; } /*logit ("done sending PCM");*/ LOCK (buf->mutex); /* Update time */ if (play_buf_fill && audio_get_bps()) buf->time += play_buf_fill / (float)audio_get_bps(); buf->hardware_buf_fill = audio_get_buf_fill(); } } UNLOCK (buf->mutex); logit ("exiting"); return NULL; } /* Allocate and initialize the buf structure, size is the buffer size. */ struct out_buf *out_buf_new (int size) { int rc; struct out_buf *buf; assert (size > 0); buf = xmalloc (sizeof (struct out_buf)); buf->buf = fifo_buf_new (size); buf->exit = 0; buf->pause = 0; buf->stop = 0; buf->time = 0.0; buf->reset_dev = 0; buf->hardware_buf_fill = 0; buf->read_thread_waiting = 0; buf->free_callback = NULL; pthread_mutex_init (&buf->mutex, NULL); pthread_cond_init (&buf->play_cond, NULL); pthread_cond_init (&buf->ready_cond, NULL); #ifdef OUT_TEST fd = open ("out_test", O_CREAT | O_TRUNC | O_WRONLY, 0600); #endif rc = pthread_create (&buf->tid, NULL, read_thread, buf); if (rc != 0) fatal ("Can't create buffer thread: %s", xstrerror (rc)); return buf; } /* Wait for empty buffer, end playing, free resources allocated for the buf * structure. Can be used only if nothing is played. */ void out_buf_free (struct out_buf *buf) { int rc; assert (buf != NULL); LOCK (buf->mutex); buf->exit = 1; pthread_cond_signal (&buf->play_cond); UNLOCK (buf->mutex); pthread_join (buf->tid, NULL); /* Let other threads using this buffer know that the state of the * buffer has changed. */ LOCK (buf->mutex); fifo_buf_clear (buf->buf); pthread_cond_broadcast (&buf->ready_cond); UNLOCK (buf->mutex); fifo_buf_free (buf->buf); buf->buf = NULL; rc = pthread_mutex_destroy (&buf->mutex); if (rc != 0) log_errno ("Destroying buffer mutex failed", rc); rc = pthread_cond_destroy (&buf->play_cond); if (rc != 0) log_errno ("Destroying buffer play condition failed", rc); rc = pthread_cond_destroy (&buf->ready_cond); if (rc != 0) log_errno ("Destroying buffer ready condition failed", rc); free (buf); logit ("buffer destroyed"); #ifdef OUT_TEST close (fd); #endif } /* Put data at the end of the buffer, return 0 if nothing was put. */ int out_buf_put (struct out_buf *buf, const char *data, int size) { int pos = 0; /*logit ("got %d bytes to play", size);*/ while (size) { int written; LOCK (buf->mutex); if (fifo_buf_get_space(buf->buf) == 0 && !buf->stop) { /*logit ("buffer full, waiting for the signal");*/ pthread_cond_wait (&buf->ready_cond, &buf->mutex); /*logit ("buffer ready");*/ } if (buf->stop) { logit ("the buffer is stopped, refusing to write to the buffer"); UNLOCK (buf->mutex); return 0; } written = fifo_buf_put (buf->buf, data + pos, size); if (written) { pthread_cond_signal (&buf->play_cond); size -= written; pos += written; } UNLOCK (buf->mutex); } return 1; } void out_buf_pause (struct out_buf *buf) { LOCK (buf->mutex); buf->pause = 1; buf->reset_dev = 1; UNLOCK (buf->mutex); } void out_buf_unpause (struct out_buf *buf) { LOCK (buf->mutex); buf->pause = 0; pthread_cond_signal (&buf->play_cond); UNLOCK (buf->mutex); } /* Stop playing, after that buffer will refuse to play anything and ignore data * sent by buf_put(). */ void out_buf_stop (struct out_buf *buf) { logit ("stopping the buffer"); LOCK (buf->mutex); buf->stop = 1; buf->pause = 0; buf->reset_dev = 1; logit ("sending signal"); pthread_cond_signal (&buf->play_cond); logit ("waiting for signal"); pthread_cond_wait (&buf->ready_cond, &buf->mutex); logit ("done"); UNLOCK (buf->mutex); } /* Reset the buffer state: this can by called ONLY when the buffer is stopped * and buf_put is not used! */ void out_buf_reset (struct out_buf *buf) { logit ("resetting the buffer"); LOCK (buf->mutex); fifo_buf_clear (buf->buf); buf->stop = 0; buf->pause = 0; buf->reset_dev = 0; buf->hardware_buf_fill = 0; UNLOCK (buf->mutex); } void out_buf_time_set (struct out_buf *buf, const float time) { LOCK (buf->mutex); buf->time = time; UNLOCK (buf->mutex); } /* Return the time in the audio which the user is currently hearing. * If unplayed samples still remain in the hardware buffer from the * previous audio then the value returned may be negative and it is * up to the caller to handle this appropriately in the context of * its own processing. */ int out_buf_time_get (struct out_buf *buf) { int time; int bps = audio_get_bps (); LOCK (buf->mutex); time = buf->time - (bps ? buf->hardware_buf_fill / (float)bps : 0); UNLOCK (buf->mutex); return time; } void out_buf_set_free_callback (struct out_buf *buf, out_buf_free_callback callback) { assert (buf != NULL); LOCK (buf->mutex); buf->free_callback = callback; UNLOCK (buf->mutex); } int out_buf_get_free (struct out_buf *buf) { int space; assert (buf != NULL); LOCK (buf->mutex); space = fifo_buf_get_space (buf->buf); UNLOCK (buf->mutex); return space; } int out_buf_get_fill (struct out_buf *buf) { int fill; assert (buf != NULL); LOCK (buf->mutex); fill = fifo_buf_get_fill (buf->buf); UNLOCK (buf->mutex); return fill; } /* Wait until the read thread will stop and wait for data to come. * This makes sure that the audio device isn't used (of course only if you * don't put anything in the buffer). */ void out_buf_wait (struct out_buf *buf) { assert (buf != NULL); logit ("Waiting for read thread to suspend..."); LOCK (buf->mutex); while (!buf->read_thread_waiting) { debug ("waiting...."); pthread_cond_wait (&buf->ready_cond, &buf->mutex); } UNLOCK (buf->mutex); logit ("done"); } moc-2.6.0~svn-r3005/out_buf.h0000664000076400000500000000147312424322530014745 0ustar riesebiesrc#ifndef BUF_H #define BUF_H #include "fifo_buf.h" #ifdef __cplusplus extern "C" { #endif typedef void out_buf_free_callback (); struct out_buf; struct out_buf *out_buf_new (int size); void out_buf_free (struct out_buf *buf); int out_buf_put (struct out_buf *buf, const char *data, int size); void out_buf_pause (struct out_buf *buf); void out_buf_unpause (struct out_buf *buf); void out_buf_stop (struct out_buf *buf); void out_buf_reset (struct out_buf *buf); void out_buf_time_set (struct out_buf *buf, const float time); int out_buf_time_get (struct out_buf *buf); void out_buf_set_free_callback (struct out_buf *buf, out_buf_free_callback callback); int out_buf_get_free (struct out_buf *buf); int out_buf_get_fill (struct out_buf *buf); void out_buf_wait (struct out_buf *buf); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/player.c0000664000076400000500000006022113333136304014567 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004-2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #define DEBUG #include "common.h" #include "log.h" #include "decoder.h" #include "audio.h" #include "out_buf.h" #include "server.h" #include "options.h" #include "player.h" #include "files.h" #include "playlist.h" #include "md5.h" #define PCM_BUF_SIZE (36 * 1024) #define PREBUFFER_THRESHOLD (18 * 1024) enum request { REQ_NOTHING, REQ_SEEK, REQ_STOP, REQ_PAUSE, REQ_UNPAUSE }; struct bitrate_list_node { struct bitrate_list_node *next; int time; int bitrate; }; /* List of points where bitrate has changed. We use it to show bitrate at the * right time when playing, because the output buffer may be big and decoding * may be many seconds ahead of what the user can hear. */ struct bitrate_list { struct bitrate_list_node *head; struct bitrate_list_node *tail; pthread_mutex_t mtx; }; struct md5_data { bool okay; long len; struct md5_ctx ctx; }; struct precache { char *file; /* the file to precache */ char buf[2 * PCM_BUF_SIZE]; /* PCM buffer with precached data */ int buf_fill; int ok; /* 1 if precache succeed */ struct sound_params sound_params; /* of the sound in the buffer */ struct decoder *f; /* decoder functions for precached file */ void *decoder_data; int running; /* if the precache thread is running */ pthread_t tid; /* tid of the precache thread */ struct bitrate_list bitrate_list; int decoded_time; /* how much sound we decoded in seconds */ }; struct precache precache; /* Request conditional and mutex. */ static pthread_cond_t request_cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t request_cond_mtx = PTHREAD_MUTEX_INITIALIZER; static enum request request = REQ_NOTHING; static int req_seek; /* Source of the played stream tags. */ static enum { TAGS_SOURCE_DECODER, /* tags from the stream (e.g., id3tags, vorbis comments) */ TAGS_SOURCE_METADATA /* tags from icecast metadata */ } tags_source; /* Tags of the currently played file. */ static struct file_tags *curr_tags = NULL; /* Mutex for curr_tags and tags_source. */ static pthread_mutex_t curr_tags_mtx = PTHREAD_MUTEX_INITIALIZER; /* Stream associated with the currently playing decoder. */ static struct io_stream *decoder_stream = NULL; static pthread_mutex_t decoder_stream_mtx = PTHREAD_MUTEX_INITIALIZER; static int prebuffering = 0; /* are we prebuffering now? */ static struct bitrate_list bitrate_list; static void bitrate_list_init (struct bitrate_list *b) { assert (b != NULL); b->head = NULL; b->tail = NULL; pthread_mutex_init (&b->mtx, NULL); } static void bitrate_list_empty (struct bitrate_list *b) { assert (b != NULL); LOCK (b->mtx); if (b->head) { while (b->head) { struct bitrate_list_node *t = b->head->next; free (b->head); b->head = t; } b->tail = NULL; } debug ("Bitrate list elements removed."); UNLOCK (b->mtx); } static void bitrate_list_destroy (struct bitrate_list *b) { int rc; assert (b != NULL); bitrate_list_empty (b); rc = pthread_mutex_destroy (&b->mtx); if (rc != 0) log_errno ("Can't destroy bitrate list mutex", rc); } static void bitrate_list_add (struct bitrate_list *b, const int time, const int bitrate) { assert (b != NULL); LOCK (b->mtx); if (!b->tail) { b->head = b->tail = (struct bitrate_list_node *)xmalloc ( sizeof(struct bitrate_list_node)); b->tail->next = NULL; b->tail->time = time; b->tail->bitrate = bitrate; debug ("Adding bitrate %d at time %d", bitrate, time); } else if (b->tail->bitrate != bitrate && b->tail->time != time) { assert (b->tail->time < time); b->tail->next = (struct bitrate_list_node *)xmalloc ( sizeof(struct bitrate_list_node)); b->tail = b->tail->next; b->tail->next = NULL; b->tail->time = time; b->tail->bitrate = bitrate; debug ("Appending bitrate %d at time %d", bitrate, time); } else if (b->tail->bitrate == bitrate) debug ("Not adding bitrate %d at time %d because the bitrate" " hasn't changed", bitrate, time); else debug ("Not adding bitrate %d at time %d because it is for" " the same time as the last bitrate", bitrate, time); UNLOCK (b->mtx); } static int bitrate_list_get (struct bitrate_list *b, const int time) { int bitrate = -1; assert (b != NULL); LOCK (b->mtx); if (b->head) { while (b->head->next && b->head->next->time <= time) { struct bitrate_list_node *o = b->head; b->head = o->next; debug ("Removing old bitrate %d for time %d", o->bitrate, o->time); free (o); } bitrate = b->head->bitrate /*b->head->time + 1000*/; debug ("Getting bitrate for time %d (%d)", time, bitrate); } else { debug ("Getting bitrate for time %d (no bitrate information)", time); bitrate = -1; } UNLOCK (b->mtx); return bitrate; } static void update_time () { static int last_time = 0; int ctime = audio_get_time (); if (ctime >= 0 && ctime != last_time) { last_time = ctime; ctime_change (); set_info_bitrate (bitrate_list_get (&bitrate_list, ctime)); } } static void *precache_thread (void *data) { struct precache *precache = (struct precache *)data; int decoded; struct sound_params new_sound_params; struct decoder_error err; precache->buf_fill = 0; precache->sound_params.channels = 0; /* mark that sound_params were not yet filled. */ precache->decoded_time = 0.0; precache->f = get_decoder (precache->file); assert (precache->f != NULL); precache->decoder_data = precache->f->open(precache->file); precache->f->get_error(precache->decoder_data, &err); if (err.type != ERROR_OK) { logit ("Failed to open the file for precache: %s", err.err); decoder_error_clear (&err); precache->f->close (precache->decoder_data); return NULL; } audio_plist_set_time (precache->file, precache->f->get_duration(precache->decoder_data)); /* Stop at PCM_BUF_SIZE, because when we decode too much, there is no * place where we can put the data that doesn't fit into the buffer. */ while (precache->buf_fill < PCM_BUF_SIZE) { decoded = precache->f->decode (precache->decoder_data, precache->buf + precache->buf_fill, PCM_BUF_SIZE, &new_sound_params); if (!decoded) { /* EOF so fast? We can't pass this information * in precache, so give up. */ logit ("EOF when precaching."); precache->f->close (precache->decoder_data); return NULL; } precache->f->get_error (precache->decoder_data, &err); if (err.type == ERROR_FATAL) { logit ("Error reading file for precache: %s", err.err); decoder_error_clear (&err); precache->f->close (precache->decoder_data); return NULL; } if (!precache->sound_params.channels) precache->sound_params = new_sound_params; else if (!sound_params_eq(precache->sound_params, new_sound_params)) { /* There is no way to store sound with two different * parameters in the buffer, give up with * precaching. (this should never happen). */ logit ("Sound parameters have changed when precaching."); decoder_error_clear (&err); precache->f->close (precache->decoder_data); return NULL; } bitrate_list_add (&precache->bitrate_list, precache->decoded_time, precache->f->get_bitrate( precache->decoder_data)); precache->buf_fill += decoded; precache->decoded_time += decoded / (float)(sfmt_Bps( new_sound_params.fmt) * new_sound_params.rate * new_sound_params.channels); if (err.type != ERROR_OK) { decoder_error_clear (&err); break; /* Don't lose the error message */ } } precache->ok = 1; logit ("Successfully precached file (%d bytes)", precache->buf_fill); return NULL; } static void start_precache (struct precache *precache, const char *file) { int rc; assert (!precache->running); assert (file != NULL); precache->file = xstrdup (file); bitrate_list_init (&precache->bitrate_list); logit ("Precaching file %s", file); precache->ok = 0; rc = pthread_create (&precache->tid, NULL, precache_thread, precache); if (rc != 0) log_errno ("Could not run precache thread", rc); else precache->running = 1; } static void precache_wait (struct precache *precache) { int rc; if (precache->running) { debug ("Waiting for precache thread..."); rc = pthread_join (precache->tid, NULL); if (rc != 0) fatal ("pthread_join() for precache thread failed: %s", xstrerror (rc)); precache->running = 0; debug ("done"); } else debug ("Precache thread is not running"); } static void precache_reset (struct precache *precache) { assert (!precache->running); precache->ok = 0; if (precache->file) { free (precache->file); precache->file = NULL; bitrate_list_destroy (&precache->bitrate_list); } } void player_init () { precache.file = NULL; precache.running = 0; precache.ok = 0; } static void show_tags (const struct file_tags *tags DEBUG_ONLY) { debug ("TAG[title]: %s", tags->title ? tags->title : "N/A"); debug ("TAG[album]: %s", tags->album ? tags->album : "N/A"); debug ("TAG[artist]: %s", tags->artist ? tags->artist : "N/A"); debug ("TAG[track]: %d", tags->track); } /* Update tags if tags from the decoder or the stream are available. */ static void update_tags (const struct decoder *f, void *decoder_data, struct io_stream *s) { char *stream_title = NULL; int tags_changed = 0; struct file_tags *new_tags; new_tags = tags_new (); LOCK (curr_tags_mtx); if (f->current_tags && f->current_tags(decoder_data, new_tags) && new_tags->title) { tags_changed = 1; tags_copy (curr_tags, new_tags); logit ("Tags change from the decoder"); tags_source = TAGS_SOURCE_DECODER; show_tags (curr_tags); } else if (s && (stream_title = io_get_metadata_title(s))) { if (curr_tags && curr_tags->title && tags_source == TAGS_SOURCE_DECODER) { logit ("New IO stream tags, ignored because there are " "decoder tags present"); free (stream_title); } else { tags_clear (curr_tags); curr_tags->title = stream_title; show_tags (curr_tags); tags_changed = 1; logit ("New IO stream tags"); tags_source = TAGS_SOURCE_METADATA; } } if (tags_changed) tags_change (); tags_free (new_tags); UNLOCK (curr_tags_mtx); } /* Called when some free space in the output buffer appears. */ static void buf_free_cb () { LOCK (request_cond_mtx); pthread_cond_broadcast (&request_cond); UNLOCK (request_cond_mtx); update_time (); } /* Decoder loop for already opened and probably running for some time decoder. * next_file will be precached at eof. */ static void decode_loop (const struct decoder *f, void *decoder_data, const char *next_file, struct out_buf *out_buf, struct sound_params *sound_params, struct md5_data *md5, const float already_decoded_sec) { bool eof = false; bool stopped = false; char buf[PCM_BUF_SIZE]; int decoded = 0; struct sound_params new_sound_params; bool sound_params_change = false; float decode_time = already_decoded_sec; /* the position of the decoder (in seconds) */ out_buf_set_free_callback (out_buf, buf_free_cb); LOCK (curr_tags_mtx); curr_tags = tags_new (); UNLOCK (curr_tags_mtx); if (f->get_stream) { LOCK (decoder_stream_mtx); decoder_stream = f->get_stream (decoder_data); UNLOCK (decoder_stream_mtx); } else logit ("No get_stream() function"); status_msg ("Playing..."); while (1) { debug ("loop..."); LOCK (request_cond_mtx); if (!eof && !decoded) { struct decoder_error err; UNLOCK (request_cond_mtx); if (decoder_stream && out_buf_get_fill(out_buf) < PREBUFFER_THRESHOLD) { prebuffering = 1; io_prebuffer (decoder_stream, options_get_int("Prebuffering") * 1024); prebuffering = 0; status_msg ("Playing..."); } decoded = f->decode (decoder_data, buf, sizeof(buf), &new_sound_params); if (decoded) decode_time += decoded / (float)(sfmt_Bps( new_sound_params.fmt) * new_sound_params.rate * new_sound_params.channels); f->get_error (decoder_data, &err); if (err.type != ERROR_OK) { md5->okay = false; if (err.type != ERROR_STREAM || options_get_bool ("ShowStreamErrors")) error ("%s", err.err); decoder_error_clear (&err); } if (!decoded) { eof = true; logit ("EOF from decoder"); } else { debug ("decoded %d bytes", decoded); if (!sound_params_eq(new_sound_params, *sound_params)) sound_params_change = true; bitrate_list_add (&bitrate_list, decode_time, f->get_bitrate(decoder_data)); update_tags (f, decoder_data, decoder_stream); } } /* Wait, if there is no space in the buffer to put the decoded * data or EOF occurred and there is something in the buffer. */ else if (decoded > out_buf_get_free(out_buf) || (eof && out_buf_get_fill(out_buf))) { debug ("waiting..."); if (eof && !precache.file && next_file && file_type(next_file) == F_SOUND && options_get_bool("Precache") && options_get_bool("AutoNext")) start_precache (&precache, next_file); pthread_cond_wait (&request_cond, &request_cond_mtx); UNLOCK (request_cond_mtx); } else UNLOCK (request_cond_mtx); /* When clearing request, we must make sure, that another * request will not arrive at the moment, so we check if * the request has changed. */ if (request == REQ_STOP) { logit ("stop"); stopped = true; md5->okay = false; out_buf_stop (out_buf); LOCK (request_cond_mtx); if (request == REQ_STOP) request = REQ_NOTHING; UNLOCK (request_cond_mtx); break; } else if (request == REQ_SEEK) { int decoder_seek; logit ("seeking"); md5->okay = false; req_seek = MAX(0, req_seek); if ((decoder_seek = f->seek(decoder_data, req_seek)) == -1) logit ("error when seeking"); else { out_buf_stop (out_buf); out_buf_reset (out_buf); out_buf_time_set (out_buf, decoder_seek); bitrate_list_empty (&bitrate_list); decode_time = decoder_seek; eof = false; decoded = 0; } LOCK (request_cond_mtx); if (request == REQ_SEEK) request = REQ_NOTHING; UNLOCK (request_cond_mtx); } else if (!eof && decoded <= out_buf_get_free(out_buf) && !sound_params_change) { debug ("putting into the buffer %d bytes", decoded); #if !defined(NDEBUG) && defined(DEBUG) if (md5->okay) { md5->len += decoded; md5_process_bytes (buf, decoded, &md5->ctx); } #endif audio_send_buf (buf, decoded); decoded = 0; } else if (!eof && sound_params_change && out_buf_get_fill(out_buf) == 0) { logit ("Sound parameters have changed."); *sound_params = new_sound_params; sound_params_change = false; set_info_channels (sound_params->channels); set_info_rate (sound_params->rate / 1000); out_buf_wait (out_buf); if (!audio_open(sound_params)) { md5->okay = false; break; } } else if (eof && out_buf_get_fill(out_buf) == 0) { logit ("played everything"); break; } } status_msg (""); LOCK (decoder_stream_mtx); decoder_stream = NULL; f->close (decoder_data); UNLOCK (decoder_stream_mtx); bitrate_list_destroy (&bitrate_list); LOCK (curr_tags_mtx); if (curr_tags) { tags_free (curr_tags); curr_tags = NULL; } UNLOCK (curr_tags_mtx); out_buf_wait (out_buf); if (precache.ok && (stopped || !options_get_bool ("AutoNext"))) { precache_wait (&precache); precache.f->close (precache.decoder_data); precache_reset (&precache); } } #if !defined(NDEBUG) && defined(DEBUG) static void log_md5_sum (const char *file, struct sound_params sound_params, const struct decoder *f, uint8_t *md5, long md5_len) { unsigned int ix, bps; char md5sum[MD5_DIGEST_SIZE * 2 + 1], format; const char *fn, *endian; for (ix = 0; ix < MD5_DIGEST_SIZE; ix += 1) sprintf (&md5sum[ix * 2], "%02x", md5[ix]); md5sum[MD5_DIGEST_SIZE * 2] = 0x00; switch (sound_params.fmt & SFMT_MASK_FORMAT) { case SFMT_S8: case SFMT_S16: case SFMT_S32: format = 's'; break; case SFMT_U8: case SFMT_U16: case SFMT_U32: format = 'u'; break; case SFMT_FLOAT: format = 'f'; break; default: debug ("Unknown sound format: 0x%04lx", sound_params.fmt); return; } bps = sfmt_Bps (sound_params.fmt) * 8; endian = ""; if (format != 'f' && bps != 8) { if (sound_params.fmt & SFMT_LE) endian = "le"; else if (sound_params.fmt & SFMT_BE) endian = "be"; } fn = strrchr (file, '/'); fn = fn ? fn + 1 : file; debug ("MD5(%s) = %s %ld %s %c%u%s %d %d", fn, md5sum, md5_len, get_decoder_name (f), format, bps, endian, sound_params.channels, sound_params.rate); } #endif /* Play a file (disk file) using the given decoder. next_file is precached. */ static void play_file (const char *file, const struct decoder *f, const char *next_file, struct out_buf *out_buf) { void *decoder_data; struct sound_params sound_params = { 0, 0, 0 }; float already_decoded_time; struct md5_data md5; #if !defined(NDEBUG) && defined(DEBUG) md5.okay = true; md5.len = 0; md5_init_ctx (&md5.ctx); #endif out_buf_reset (out_buf); precache_wait (&precache); if (precache.ok && strcmp(precache.file, file)) { logit ("The precached file is not the file we want."); precache.f->close (precache.decoder_data); precache_reset (&precache); } if (precache.ok && !strcmp(precache.file, file)) { struct decoder_error err; logit ("Using precached file"); assert (f == precache.f); sound_params = precache.sound_params; decoder_data = precache.decoder_data; set_info_channels (sound_params.channels); set_info_rate (sound_params.rate / 1000); if (!audio_open(&sound_params)) { md5.okay = false; precache.f->close (precache.decoder_data); precache_reset (&precache); return; } #if !defined(NDEBUG) && defined(DEBUG) md5.len += precache.buf_fill; md5_process_bytes (precache.buf, precache.buf_fill, &md5.ctx); #endif audio_send_buf (precache.buf, precache.buf_fill); precache.f->get_error (precache.decoder_data, &err); if (err.type != ERROR_OK) { md5.okay = false; if (err.type != ERROR_STREAM || options_get_bool ("ShowStreamErrors")) error ("%s", err.err); decoder_error_clear (&err); } already_decoded_time = precache.decoded_time; if(f->get_avg_bitrate) set_info_avg_bitrate (f->get_avg_bitrate(decoder_data)); else set_info_avg_bitrate (0); bitrate_list_init (&bitrate_list); bitrate_list.head = precache.bitrate_list.head; bitrate_list.tail = precache.bitrate_list.tail; /* don't free list elements when resetting precache */ precache.bitrate_list.head = NULL; precache.bitrate_list.tail = NULL; } else { struct decoder_error err; status_msg ("Opening..."); decoder_data = f->open(file); f->get_error (decoder_data, &err); if (err.type != ERROR_OK) { f->close (decoder_data); status_msg (""); error ("%s", err.err); decoder_error_clear (&err); logit ("Can't open file, exiting"); return; } already_decoded_time = 0.0; if (f->get_avg_bitrate) set_info_avg_bitrate (f->get_avg_bitrate(decoder_data)); bitrate_list_init (&bitrate_list); } audio_plist_set_time (file, f->get_duration(decoder_data)); audio_state_started_playing (); precache_reset (&precache); decode_loop (f, decoder_data, next_file, out_buf, &sound_params, &md5, already_decoded_time); #if !defined(NDEBUG) && defined(DEBUG) if (md5.okay) { uint8_t buf[MD5_DIGEST_SIZE]; md5_finish_ctx (&md5.ctx, buf); log_md5_sum (file, sound_params, f, buf, md5.len); } #endif } /* Play the stream (global decoder_stream) using the given decoder. */ static void play_stream (const struct decoder *f, struct out_buf *out_buf) { void *decoder_data; struct sound_params sound_params = { 0, 0, 0 }; struct decoder_error err; struct md5_data null_md5; null_md5.okay = false; out_buf_reset (out_buf); assert (f->open_stream != NULL); decoder_data = f->open_stream (decoder_stream); f->get_error (decoder_data, &err); if (err.type != ERROR_OK) { LOCK (decoder_stream_mtx); decoder_stream = NULL; UNLOCK (decoder_stream_mtx); f->close (decoder_data); error ("%s", err.err); status_msg (""); decoder_error_clear (&err); logit ("Can't open file"); } else { audio_state_started_playing (); bitrate_list_init (&bitrate_list); decode_loop (f, decoder_data, NULL, out_buf, &sound_params, &null_md5, 0.0); } } /* Callback for io buffer fill - show the prebuffering state. */ static void fill_cb (struct io_stream *unused1 ATTR_UNUSED, size_t fill, size_t unused2 ATTR_UNUSED, void *unused3 ATTR_UNUSED) { if (prebuffering) { char msg[64]; sprintf (msg, "Prebuffering %zu/%d KB", fill / 1024U, options_get_int("Prebuffering")); status_msg (msg); } } /* Open a file, decode it and put output into the buffer. At the end, start * precaching next_file. */ void player (const char *file, const char *next_file, struct out_buf *out_buf) { struct decoder *f; if (file_type(file) == F_URL) { status_msg ("Connecting..."); LOCK (decoder_stream_mtx); decoder_stream = io_open (file, 1); if (!io_ok(decoder_stream)) { error ("Could not open URL: %s", io_strerror(decoder_stream)); io_close (decoder_stream); status_msg (""); decoder_stream = NULL; UNLOCK (decoder_stream_mtx); return; } UNLOCK (decoder_stream_mtx); f = get_decoder_by_content (decoder_stream); if (!f) { LOCK (decoder_stream_mtx); io_close (decoder_stream); status_msg (""); decoder_stream = NULL; UNLOCK (decoder_stream_mtx); return; } status_msg ("Prebuffering..."); prebuffering = 1; io_set_buf_fill_callback (decoder_stream, fill_cb, NULL); io_prebuffer (decoder_stream, options_get_int("Prebuffering") * 1024); prebuffering = 0; status_msg ("Playing..."); ev_audio_start (); play_stream (f, out_buf); ev_audio_stop (); } else { f = get_decoder (file); LOCK (decoder_stream_mtx); decoder_stream = NULL; UNLOCK (decoder_stream_mtx); if (!f) { error ("Can't get decoder for %s", file); return; } ev_audio_start (); play_file (file, f, next_file, out_buf); ev_audio_stop (); } logit ("exiting"); } void player_cleanup () { int rc; rc = pthread_mutex_destroy (&request_cond_mtx); if (rc != 0) log_errno ("Can't destroy request mutex", rc); rc = pthread_mutex_destroy (&curr_tags_mtx); if (rc != 0) log_errno ("Can't destroy tags mutex", rc); rc = pthread_mutex_destroy (&decoder_stream_mtx); if (rc != 0) log_errno ("Can't destroy decoder_stream mutex", rc); rc = pthread_cond_destroy (&request_cond); if (rc != 0) log_errno ("Can't destroy request condition", rc); precache_wait (&precache); precache_reset (&precache); } void player_reset () { request = REQ_NOTHING; } void player_stop () { logit ("requesting stop"); request = REQ_STOP; LOCK (decoder_stream_mtx); if (decoder_stream) { logit ("decoder_stream present, aborting..."); io_abort (decoder_stream); } UNLOCK (decoder_stream_mtx); LOCK (request_cond_mtx); pthread_cond_signal (&request_cond); UNLOCK (request_cond_mtx); } void player_seek (const int sec) { int time; time = audio_get_time (); if (time >= 0) { request = REQ_SEEK; req_seek = sec + time; LOCK (request_cond_mtx); pthread_cond_signal (&request_cond); UNLOCK (request_cond_mtx); } } void player_jump_to (const int sec) { request = REQ_SEEK; req_seek = sec; LOCK (request_cond_mtx); pthread_cond_signal (&request_cond); UNLOCK (request_cond_mtx); } /* Stop playing, clear the output buffer, but allow to unpause by starting * playing the same stream. This is useful for Internet streams that can't * be really paused. */ void player_pause () { request = REQ_PAUSE; LOCK (request_cond_mtx); pthread_cond_signal (&request_cond); UNLOCK (request_cond_mtx); } void player_unpause () { request = REQ_UNPAUSE; LOCK (request_cond_mtx); pthread_cond_signal (&request_cond); UNLOCK (request_cond_mtx); } /* Return tags for the currently played file or NULL if there are no tags. * Tags are duplicated. */ struct file_tags *player_get_curr_tags () { struct file_tags *tags = NULL; LOCK (curr_tags_mtx); if (curr_tags) tags = tags_dup (curr_tags); UNLOCK (curr_tags_mtx); return tags; } moc-2.6.0~svn-r3005/player.h0000664000076400000500000000076011322047704014577 0ustar riesebiesrc#ifndef PLAYER_H #define PLAYER_H #include "out_buf.h" #include "io.h" #include "playlist.h" #ifdef __cplusplus extern "C" { #endif void player_cleanup (); void player (const char *file, const char *next_file, struct out_buf *out_buf); void player_stop (); void player_seek (const int n); void player_jump_to (const int n); void player_reset (); void player_init (); struct file_tags *player_get_curr_tags (); void player_pause (); void player_unpause (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/playlist_file.c0000664000076400000500000002403213333460607016141 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004 - 2006 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "playlist.h" #include "playlist_file.h" #include "log.h" #include "files.h" #include "options.h" #include "interface.h" #include "decoder.h" int is_plist_file (const char *name) { const char *ext = ext_pos (name); if (ext && (!strcasecmp(ext, "m3u") || !strcasecmp(ext, "pls"))) return 1; return 0; } static void make_path (char *buf, size_t buf_size, const char *cwd, char *path) { if (file_type(path) == F_URL) { strncpy (buf, path, buf_size); buf[buf_size-1] = 0; return; } if (path[0] != '/') strcpy (buf, cwd); else strcpy (buf, "/"); resolve_path (buf, buf_size, path); } /* Strip white chars from the end of a string. */ static void strip_string (char *str) { char *c = str; char *last_non_white = str; while (*c) { if (!isblank(*c)) last_non_white = c; c++; } if (c > last_non_white) *(last_non_white + 1) = 0; } /* Load M3U file into plist. Return the number of items read. */ static int plist_load_m3u (struct plist *plist, const char *fname, const char *cwd, const int load_serial) { FILE *file; char *line = NULL; int last_added = -1; int after_extinf = 0; int added = 0; struct flock read_lock = {.l_type = F_RDLCK, .l_whence = SEEK_SET}; file = fopen (fname, "r"); if (!file) { error_errno ("Can't open playlist file", errno); return 0; } /* Lock gets released by fclose(). */ if (fcntl (fileno (file), F_SETLKW, &read_lock) == -1) log_errno ("Can't lock the playlist file", errno); while ((line = read_line (file))) { if (!strncmp (line, "#EXTINF:", sizeof("#EXTINF:") - 1)) { char *comma, *num_err; char time_text[10] = ""; int time_sec; if (after_extinf) { error ("Broken M3U file: double #EXTINF!"); plist_delete (plist, last_added); goto err; } /* Find the comma */ comma = strchr (line + (sizeof("#EXTINF:") - 1), ','); if (!comma) { error ("Broken M3U file: no comma in #EXTINF!"); goto err; } /* Get the time string */ time_text[sizeof(time_text) - 1] = 0; strncpy (time_text, line + sizeof("#EXTINF:") - 1, MIN(comma - line - (sizeof("#EXTINF:") - 1), sizeof(time_text))); if (time_text[sizeof(time_text) - 1]) { error ("Broken M3U file: wrong time!"); goto err; } /* Extract the time. */ time_sec = strtol (time_text, &num_err, 10); if (*num_err) { error ("Broken M3U file: time is not a number!"); goto err; } after_extinf = 1; last_added = plist_add (plist, NULL); plist_set_title_tags (plist, last_added, comma + 1); if (*time_text) plist_set_item_time (plist, last_added, time_sec); } else if (line[0] != '#') { char path[2 * PATH_MAX]; strip_string (line); if (strlen (line) <= PATH_MAX) { make_path (path, sizeof(path), cwd, line); if (plist_find_fname (plist, path) == -1) { if (after_extinf) plist_set_file (plist, last_added, path); else plist_add (plist, path); added += 1; } else if (after_extinf) plist_delete (plist, last_added); } else if (after_extinf) plist_delete (plist, last_added); after_extinf = 0; } else if (load_serial && !strncmp (line, "#MOCSERIAL: ", sizeof("#MOCSERIAL: ") - 1)) { char *serial_str = line + sizeof("#MOCSERIAL: ") - 1; if (serial_str[0]) { char *err; long serial; serial = strtol (serial_str, &err, 0); if (!*err) { plist_set_serial (plist, serial); logit ("Got MOCSERIAL tag with serial %ld", serial); } } } free (line); } err: free (line); fclose (file); return added; } /* Return 1 if the line contains only blank characters, 0 otherwise. */ static int is_blank_line (const char *l) { while (*l && isblank(*l)) l++; if (*l) return 0; return 1; } /* Read a value from the given section from .INI file. File should be opened * and seeking will be performed on it. Return the malloc()ed value or NULL * if not present or error occurred. */ static char *read_ini_value (FILE *file, const char *section, const char *key) { char *line = NULL; int in_section = 0; char *value = NULL; int key_len; if (fseek(file, 0, SEEK_SET)) { error_errno ("File fseek() error", errno); return NULL; } key_len = strlen (key); while ((line = read_line(file))) { if (line[0] == '[') { if (in_section) { /* we are outside of the interesting section */ free (line); break; } else { char *close = strchr (line, ']'); if (!close) { error ("Parse error in the INI file"); free (line); break; } if (!strncasecmp(line + 1, section, close - line - 1)) in_section = 1; } } else if (in_section && line[0] != '#' && !is_blank_line(line)) { char *t, *t2; t2 = t = strchr (line, '='); if (!t) { error ("Parse error in the INI file"); free (line); break; } /* go back to the last char in the name */ while (t2 >= t && (isblank(*t2) || *t2 == '=')) t2--; if (t2 == t) { error ("Parse error in the INI file"); free (line); break; } if (!strncasecmp(line, key, MAX(t2 - line + 1, key_len))) { value = t + 1; while (isblank(value[0])) value++; if (value[0] == '"') { char *q = strchr (value + 1, '"'); if (!q) { error ("Parse error in the INI file"); free (line); break; } *q = 0; } value = xstrdup (value); free (line); break; } } free (line); } return value; } /* Load PLS file into plist. Return the number of items read. */ static int plist_load_pls (struct plist *plist, const char *fname, const char *cwd) { FILE *file; char *e, *line = NULL; long i, nitems, added = 0; file = fopen (fname, "r"); if (!file) { error_errno ("Can't open playlist file", errno); return 0; } line = read_ini_value (file, "playlist", "NumberOfEntries"); if (!line) { /* Assume that it is a pls file version 1 - plist_load_m3u() * should handle it like an m3u file without the m3u extensions. */ fclose (file); return plist_load_m3u (plist, fname, cwd, 0); } nitems = strtol (line, &e, 10); if (*e) { error ("Broken PLS file"); goto err; } for (i = 1; i <= nitems; i++) { int time, last_added; char *pls_file, *pls_title, *pls_length; char key[32], path[2 * PATH_MAX]; sprintf (key, "File%ld", i); pls_file = read_ini_value (file, "playlist", key); if (!pls_file) { error ("Broken PLS file"); goto err; } sprintf (key, "Title%ld", i); pls_title = read_ini_value (file, "playlist", key); sprintf (key, "Length%ld", i); pls_length = read_ini_value (file, "playlist", key); if (pls_length) { time = strtol (pls_length, &e, 10); if (*e) time = -1; } else time = -1; if (strlen (pls_file) <= PATH_MAX) { make_path (path, sizeof(path), cwd, pls_file); if (plist_find_fname (plist, path) == -1) { last_added = plist_add (plist, path); if (pls_title && pls_title[0]) plist_set_title_tags (plist, last_added, pls_title); if (time > 0) { plist->items[last_added].tags = tags_new (); plist->items[last_added].tags->time = time; plist->items[last_added].tags->filled |= TAGS_TIME; } } } free (pls_file); if (pls_title) free (pls_title); if (pls_length) free (pls_length); added += 1; } err: free (line); fclose (file); return added; } /* Load a playlist into plist. Return the number of items on the list. */ /* The playlist may have deleted items. */ int plist_load (struct plist *plist, const char *fname, const char *cwd, const int load_serial) { int num, read_tags; const char *ext; read_tags = options_get_bool ("ReadTags"); ext = ext_pos (fname); if (ext && !strcasecmp(ext, "pls")) num = plist_load_pls (plist, fname, cwd); else num = plist_load_m3u (plist, fname, cwd, load_serial); if (read_tags) switch_titles_tags (plist); else switch_titles_file (plist); return num; } /* Save the playlist into the file in m3u format. If save_serial is not 0, * the playlist serial is saved in a comment. */ int plist_save (struct plist *plist, const char *fname, const int save_serial) { FILE *file = NULL; int i, ret, result = 0; struct flock write_lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET}; debug ("Saving playlist to '%s'", fname); file = fopen (fname, "w"); if (!file) { error_errno ("Can't save playlist", errno); return 0; } /* Lock gets released by fclose(). */ if (fcntl (fileno (file), F_SETLKW, &write_lock) == -1) log_errno ("Can't lock the playlist file", errno); if (fprintf (file, "#EXTM3U\r\n") < 0) { error_errno ("Error writing playlist", errno); goto err; } if (save_serial && fprintf (file, "#MOCSERIAL: %d\r\n", plist_get_serial (plist)) < 0) { error_errno ("Error writing playlist", errno); goto err; } for (i = 0; i < plist->num; i++) { if (!plist_deleted (plist, i)) { /* EXTM3U */ if (plist->items[i].tags) ret = fprintf (file, "#EXTINF:%d,%s\r\n", plist->items[i].tags->time, plist->items[i].title_tags ? plist->items[i].title_tags : plist->items[i].title_file); else ret = fprintf (file, "#EXTINF:%d,%s\r\n", 0, plist->items[i].title_file); /* file */ if (ret >= 0) ret = fprintf (file, "%s\r\n", plist->items[i].file); if (ret < 0) { error_errno ("Error writing playlist", errno); goto err; } } } ret = fclose (file); file = NULL; if (ret) error_errno ("Error writing playlist", errno); else result = 1; err: if (file) fclose (file); return result; } moc-2.6.0~svn-r3005/playlist_file.h0000664000076400000500000000052613035552665016155 0ustar riesebiesrc#ifndef PLAYLIST_FILE_H #define PLAYLIST_FILE_H #ifdef __cplusplus extern "C" { #endif int plist_load (struct plist *plist, const char *fname, const char *cwd, const int load_serial); int plist_save (struct plist *plist, const char *file, const int save_serial); int is_plist_file (const char *name); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/protocol.c0000664000076400000500000003762312707044036015152 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2003 - 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "common.h" #include "log.h" #include "protocol.h" #include "playlist.h" #include "files.h" /* Maximal socket name. */ #define UNIX_PATH_MAX 108 #define SOCKET_NAME "socket2" #define nonblocking(fn, result, sock, buf, len) \ do { \ long flags = fcntl (sock, F_GETFL); \ if (flags == -1) \ fatal ("Getting flags for socket failed: %s", \ xstrerror (errno)); \ flags |= O_NONBLOCK; \ if (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) \ fatal ("Setting O_NONBLOCK for the socket failed: %s", \ xstrerror (errno)); \ result = fn (sock, buf, len, 0); \ flags &= ~O_NONBLOCK; \ if (fcntl (sock, F_SETFL, flags) == -1) \ fatal ("Restoring flags for socket failed: %s", \ xstrerror (errno)); \ } while (0) /* Buffer used to send data in one bigger chunk instead of sending sigle * integer, string etc. values. */ struct packet_buf { char *buf; size_t allocated; size_t len; }; /* Create a socket name, return NULL if the name could not be created. */ char *socket_name () { char *socket_name = create_file_name (SOCKET_NAME); if (strlen(socket_name) > UNIX_PATH_MAX) fatal ("Can't create socket name!"); return socket_name; } /* Get an integer value from the socket, return == 0 on error. */ int get_int (int sock, int *i) { ssize_t res; res = recv (sock, i, sizeof(int), 0); if (res == -1) log_errno ("recv() failed when getting int", errno); return res == ssizeof(int) ? 1 : 0; } /* Get an integer value from the socket without blocking. */ enum noblock_io_status get_int_noblock (int sock, int *i) { ssize_t res; char *err; nonblocking (recv, res, sock, i, sizeof (int)); if (res == ssizeof (int)) return NB_IO_OK; if (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return NB_IO_BLOCK; err = xstrerror (errno); logit ("recv() failed when getting int (res %zd): %s", res, err); free (err); return NB_IO_ERR; } /* Send an integer value to the socket, return == 0 on error */ int send_int (int sock, int i) { ssize_t res; res = send (sock, &i, sizeof(int), 0); if (res == -1) log_errno ("send() failed", errno); return res == ssizeof(int) ? 1 : 0; } #if 0 /* Get a long value from the socket, return == 0 on error. */ static int get_long (int sock, long *i) { ssize_t res; res = recv (sock, i, sizeof(long), 0); if (res == -1) log_errno ("recv() failed when getting int", errno); return res == ssizeof(long) ? 1 : 0; } #endif #if 0 /* Send a long value to the socket, return == 0 on error */ static int send_long (int sock, long i) { ssize_t res; res = send (sock, &i, sizeof(long), 0); if (res == -1) log_errno ("send() failed", errno); return res == ssizeof(long) ? 1 : 0; } #endif /* Get the string from socket, return NULL on error. The memory is malloced. */ char *get_str (int sock) { int len, nread = 0; char *str; if (!get_int(sock, &len)) return NULL; if (!RANGE(0, len, MAX_SEND_STRING)) { logit ("Bad string length."); return NULL; } str = (char *)xmalloc (sizeof(char) * (len + 1)); while (nread < len) { ssize_t res; res = recv (sock, str + nread, len - nread, 0); if (res == -1) { log_errno ("recv() failed when getting string", errno); free (str); return NULL; } if (res == 0) { logit ("Unexpected EOF when getting string"); free (str); return NULL; } nread += res; } str[len] = 0; return str; } int send_str (int sock, const char *str) { int len; len = strlen (str); if (!send_int (sock, len)) return 0; if (send (sock, str, len, 0) != len) return 0; return 1; } /* Get a time_t value from the socket, return == 0 on error. */ int get_time (int sock, time_t *i) { ssize_t res; res = recv (sock, i, sizeof(time_t), 0); if (res == -1) log_errno ("recv() failed when getting time_t", errno); return res == ssizeof(time_t) ? 1 : 0; } /* Send a time_t value to the socket, return == 0 on error */ int send_time (int sock, time_t i) { ssize_t res; res = send (sock, &i, sizeof(time_t), 0); if (res == -1) log_errno ("send() failed", errno); return res == ssizeof(time_t) ? 1 : 0; } static struct packet_buf *packet_buf_new () { struct packet_buf *b; b = (struct packet_buf *)xmalloc (sizeof(struct packet_buf)); b->buf = (char *)xmalloc (1024); b->allocated = 1024; b->len = 0; return b; } static void packet_buf_free (struct packet_buf *b) { assert (b != NULL); free (b->buf); free (b); } /* Make sure that there is at least len bytes free. */ static void packet_buf_add_space (struct packet_buf *b, const size_t len) { assert (b != NULL); if (b->allocated < b->len + len) { b->allocated += len + 256; /* put some more space */ b->buf = (char *)xrealloc (b->buf, b->allocated); } } /* Add an integer value to the buffer */ static void packet_buf_add_int (struct packet_buf *b, const int n) { assert (b != NULL); packet_buf_add_space (b, sizeof(n)); memcpy (b->buf + b->len, &n, sizeof(n)); b->len += sizeof(n); } /* Add a string value to the buffer. */ static void packet_buf_add_str (struct packet_buf *b, const char *str) { int str_len; assert (b != NULL); assert (str != NULL); str_len = strlen (str); packet_buf_add_int (b, str_len); packet_buf_add_space (b, str_len * sizeof(char)); memcpy (b->buf + b->len, str, str_len * sizeof(char)); b->len += str_len * sizeof(char); } /* Add a time_t value to the buffer. */ static void packet_buf_add_time (struct packet_buf *b, const time_t n) { assert (b != NULL); packet_buf_add_space (b, sizeof(n)); memcpy (b->buf + b->len, &n, sizeof(n)); b->len += sizeof(n); } /* Add tags to the buffer. If tags == NULL, add empty tags. */ void packet_buf_add_tags (struct packet_buf *b, const struct file_tags *tags) { assert (b != NULL); if (tags) { packet_buf_add_str (b, tags->title ? tags->title : ""); packet_buf_add_str (b, tags->artist ? tags->artist : ""); packet_buf_add_str (b, tags->album ? tags->album : ""); packet_buf_add_int (b, tags->track); packet_buf_add_int (b, tags->filled & TAGS_TIME ? tags->time : -1); packet_buf_add_int (b, tags->filled); } else { /* empty tags: */ packet_buf_add_str (b, ""); /* title */ packet_buf_add_str (b, ""); /* artist */ packet_buf_add_str (b, ""); /* album */ packet_buf_add_int (b, -1); /* track */ packet_buf_add_int (b, -1); /* time */ packet_buf_add_int (b, 0); /* filled */ } } /* Add an item to the buffer. */ void packet_buf_add_item (struct packet_buf *b, const struct plist_item *item) { packet_buf_add_str (b, item->file); packet_buf_add_str (b, item->title_tags ? item->title_tags : ""); packet_buf_add_tags (b, item->tags); packet_buf_add_time (b, item->mtime); } /* Send data to the socket. Return 0 on error. */ static int send_all (int sock, const char *buf, const size_t size) { ssize_t sent; size_t send_pos = 0; while (send_pos < size) { sent = send (sock, buf + send_pos, size - send_pos, 0); if (sent < 0) { log_errno ("Error while sending data", errno); return 0; } send_pos += sent; } return 1; } /* Send a playlist item to the socket. If item == NULL, send empty item mark * (end of playlist). Return 0 on error. */ int send_item (int sock, const struct plist_item *item) { int res = 1; struct packet_buf *b; if (!item) { if (!send_str(sock, "")) { logit ("Error while sending empty item"); return 0; } return 1; } b = packet_buf_new (); packet_buf_add_item (b, item); if (!send_all(sock, b->buf, b->len)) { logit ("Error when sending item"); res = 0; } packet_buf_free (b); return res; } struct file_tags *recv_tags (int sock) { struct file_tags *tags = tags_new (); if (!(tags->title = get_str(sock))) { logit ("Error while receiving title"); tags_free (tags); return NULL; } if (!(tags->artist = get_str(sock))) { logit ("Error while receiving artist"); tags_free (tags); return NULL; } if (!(tags->album = get_str(sock))) { logit ("Error while receiving album"); tags_free (tags); return NULL; } if (!get_int(sock, &tags->track)) { logit ("Error while receiving track"); tags_free (tags); return NULL; } if (!get_int(sock, &tags->time)) { logit ("Error while receiving time"); tags_free (tags); return NULL; } if (!get_int(sock, &tags->filled)) { logit ("Error while receiving 'filled'"); tags_free (tags); return NULL; } /* Set NULL instead of empty tags. */ if (!tags->title[0]) { free (tags->title); tags->title = NULL; } if (!tags->artist[0]) { free (tags->artist); tags->artist = NULL; } if (!tags->album[0]) { free (tags->album); tags->album = NULL; } return tags; } /* Send tags. If tags == NULL, send empty tags. Return 0 on error. */ int send_tags (int sock, const struct file_tags *tags) { int res = 1; struct packet_buf *b; b = packet_buf_new (); packet_buf_add_tags (b, tags); if (!send_all(sock, b->buf, b->len)) res = 0; packet_buf_free (b); return res; } /* Get a playlist item from the server. * The end of the playlist is indicated by item->file being an empty string. * The memory is malloc()ed. Returns NULL on error. */ struct plist_item *recv_item (int sock) { struct plist_item *item = plist_new_item (); /* get the file name */ if (!(item->file = get_str(sock))) { logit ("Error while receiving file name"); free (item); return NULL; } if (item->file[0]) { if (!(item->title_tags = get_str(sock))) { logit ("Error while receiving tags title"); free (item->file); free (item); return NULL; } item->type = file_type (item->file); if (!item->title_tags[0]) { free (item->title_tags); item->title_tags = NULL; } if (!(item->tags = recv_tags(sock))) { logit ("Error while receiving tags"); free (item->file); if (item->title_tags) free (item->title_tags); free (item); return NULL; } if (!get_time(sock, &item->mtime)) { logit ("Error while receiving mtime"); if (item->title_tags) free (item->title_tags); free (item->file); tags_free (item->tags); free (item); return NULL; } } return item; } struct move_ev_data *recv_move_ev_data (int sock) { struct move_ev_data *d; d = (struct move_ev_data *)xmalloc (sizeof(struct move_ev_data)); if (!(d->from = get_str(sock))) { logit ("Error while receiving 'from' data"); free (d); return NULL; } if (!(d->to = get_str(sock))) { logit ("Error while receiving 'to' data"); free (d->from); free (d); return NULL; } return d; } /* Push an event on the queue if it's not already there. */ void event_push (struct event_queue *q, const int event, void *data) { assert (q != NULL); if (!q->head) { q->head = (struct event *)xmalloc (sizeof(struct event)); q->head->next = NULL; q->head->type = event; q->head->data = data; q->tail = q->head; } else { assert (q->head != NULL); assert (q->tail != NULL); assert (q->tail->next == NULL); q->tail->next = (struct event *)xmalloc ( sizeof(struct event)); q->tail = q->tail->next; q->tail->next = NULL; q->tail->type = event; q->tail->data = data; } } /* Remove the first event from the queue (don't free the data field). */ void event_pop (struct event_queue *q) { struct event *e; assert (q != NULL); assert (q->head != NULL); assert (q->tail != NULL); e = q->head; q->head = e->next; free (e); if (q->tail == e) q->tail = NULL; /* the queue is empty */ } /* Get the pointer to the first item in the queue or NULL if the queue is * empty. */ struct event *event_get_first (struct event_queue *q) { assert (q != NULL); return q->head; } void free_tag_ev_data (struct tag_ev_response *d) { assert (d != NULL); free (d->file); tags_free (d->tags); free (d); } void free_move_ev_data (struct move_ev_data *m) { assert (m != NULL); assert (m->from != NULL); assert (m->to != NULL); free (m->to); free (m->from); free (m); } struct move_ev_data *move_ev_data_dup (const struct move_ev_data *m) { struct move_ev_data *new; assert (m != NULL); assert (m->from != NULL); assert (m->to != NULL); new = (struct move_ev_data *)xmalloc (sizeof(struct move_ev_data)); new->from = xstrdup (m->from); new->to = xstrdup (m->to); return new; } /* Free data associated with the event if any. */ void free_event_data (const int type, void *data) { if (type == EV_PLIST_ADD || type == EV_QUEUE_ADD) { plist_free_item_fields ((struct plist_item *)data); free (data); } else if (type == EV_FILE_TAGS) free_tag_ev_data ((struct tag_ev_response *)data); else if (type == EV_PLIST_DEL || type == EV_STATUS_MSG || type == EV_SRV_ERROR || type == EV_QUEUE_DEL) free (data); else if (type == EV_PLIST_MOVE || type == EV_QUEUE_MOVE) free_move_ev_data ((struct move_ev_data *)data); else if (data) abort (); /* BUG */ } /* Free event queue content without the queue structure. */ void event_queue_free (struct event_queue *q) { struct event *e; assert (q != NULL); while ((e = event_get_first(q))) { free_event_data (e->type, e->data); event_pop (q); } } void event_queue_init (struct event_queue *q) { assert (q != NULL); q->head = NULL; q->tail = NULL; } #if 0 /* Search for an event of this type and return pointer to it or NULL if there * was no such event. */ struct event *event_search (struct event_queue *q, const int event) { struct event *e; assert (q != NULL); while ((e = q->head)) { if (e->type == event) return e; e = e->next; } return NULL; } #endif /* Return != 0 if the queue is empty. */ int event_queue_empty (const struct event_queue *q) { assert (q != NULL); return q->head == NULL ? 1 : 0; } /* Make a packet buffer filled with the event (with data). */ static struct packet_buf *make_event_packet (const struct event *e) { struct packet_buf *b; assert (e != NULL); b = packet_buf_new (); packet_buf_add_int (b, e->type); if (e->type == EV_PLIST_DEL || e->type == EV_QUEUE_DEL || e->type == EV_SRV_ERROR || e->type == EV_STATUS_MSG) { assert (e->data != NULL); packet_buf_add_str (b, e->data); } else if (e->type == EV_PLIST_ADD || e->type == EV_QUEUE_ADD) { assert (e->data != NULL); packet_buf_add_item (b, e->data); } else if (e->type == EV_FILE_TAGS) { struct tag_ev_response *r; assert (e->data != NULL); r = e->data; packet_buf_add_str (b, r->file); packet_buf_add_tags (b, r->tags); } else if (e->type == EV_PLIST_MOVE || e->type == EV_QUEUE_MOVE) { struct move_ev_data *m; assert (e->data != NULL); m = (struct move_ev_data *)e->data; packet_buf_add_str (b, m->from); packet_buf_add_str (b, m->to); } else if (e->data) abort (); /* BUG */ return b; } /* Send the first event from the queue and remove it on success. If the * operation would block return NB_IO_BLOCK. Return NB_IO_ERR on error * or NB_IO_OK on success. */ enum noblock_io_status event_send_noblock (int sock, struct event_queue *q) { ssize_t res; char *err; struct packet_buf *b; enum noblock_io_status result; assert (q != NULL); assert (!event_queue_empty(q)); b = make_event_packet (event_get_first(q)); /* We must do it in one send() call to be able to handle blocking. */ nonblocking (send, res, sock, b->buf, b->len); if (res == (ssize_t)b->len) { struct event *e; e = event_get_first (q); free_event_data (e->type, e->data); event_pop (q); result = NB_IO_OK; goto exit; } if (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { logit ("Sending event would block"); result = NB_IO_BLOCK; goto exit; } err = xstrerror (errno); logit ("send()ing event failed (%zd): %s", res, err); free (err); result = NB_IO_ERR; exit: packet_buf_free (b); return result; } moc-2.6.0~svn-r3005/protocol.h0000664000076400000500000001620612345426011015144 0ustar riesebiesrc#ifndef PROTOCOL_H #define PROTOCOL_H #include "playlist.h" #ifdef __cplusplus extern "C" { #endif struct event { int type; /* type of the event (one of EV_*) */ void *data; /* optional data associated with the event */ struct event *next; }; struct event_queue { struct event *head; struct event *tail; }; /* Used as data field in the event queue for EV_FILE_TAGS. */ struct tag_ev_response { char *file; struct file_tags *tags; }; /* Used as data field in the event queue for EV_PLIST_MOVE. */ struct move_ev_data { /* Two files that are to be exchanged. */ char *from; char *to; }; /* Status of nonblock sending/receiving function. */ enum noblock_io_status { NB_IO_OK, NB_IO_BLOCK, NB_IO_ERR }; /* Definition of events sent by server to the client. */ #define EV_STATE 0x01 /* server has changed the state */ #define EV_CTIME 0x02 /* current time of the song has changed */ #define EV_SRV_ERROR 0x04 /* an error occurred */ #define EV_BUSY 0x05 /* another client is connected to the server */ #define EV_DATA 0x06 /* data in response to a request will arrive */ #define EV_BITRATE 0x07 /* the bitrate has changed */ #define EV_RATE 0x08 /* the rate has changed */ #define EV_CHANNELS 0x09 /* the number of channels has changed */ #define EV_EXIT 0x0a /* the server is about to exit */ #define EV_PONG 0x0b /* response for CMD_PING */ #define EV_OPTIONS 0x0c /* the options has changed */ #define EV_SEND_PLIST 0x0d /* request for sending the playlist */ #define EV_TAGS 0x0e /* tags for the current file have changed */ #define EV_STATUS_MSG 0x0f /* followed by a status message */ #define EV_MIXER_CHANGE 0x10 /* the mixer channel was changed */ #define EV_FILE_TAGS 0x11 /* tags in a response for tags request */ #define EV_AVG_BITRATE 0x12 /* average bitrate has changed (new song) */ #define EV_AUDIO_START 0x13 /* playing of audio has started */ #define EV_AUDIO_STOP 0x14 /* playing of audio has stopped */ /* Events caused by a client that wants to modify the playlist (see * CMD_CLI_PLIST* commands). */ #define EV_PLIST_ADD 0x50 /* add an item, followed by the file name */ #define EV_PLIST_DEL 0x51 /* delete an item, followed by the file name */ #define EV_PLIST_MOVE 0x52 /* move an item, followed by 2 file names */ #define EV_PLIST_CLEAR 0x53 /* clear the playlist */ /* These events, though similar to the four previous are caused by server * which takes care of clients' queue synchronization. */ #define EV_QUEUE_ADD 0x54 #define EV_QUEUE_DEL 0x55 #define EV_QUEUE_MOVE 0x56 #define EV_QUEUE_CLEAR 0x57 /* State of the server. */ #define STATE_PLAY 0x01 #define STATE_STOP 0x02 #define STATE_PAUSE 0x03 /* Definition of server commands. */ #define CMD_PLAY 0x00 /* play the first element on the list */ #define CMD_LIST_CLEAR 0x01 /* clear the list */ #define CMD_LIST_ADD 0x02 /* add an item to the list */ #define CMD_STOP 0x04 /* stop playing */ #define CMD_PAUSE 0x05 /* pause */ #define CMD_UNPAUSE 0x06 /* unpause */ #define CMD_SET_OPTION 0x07 /* set an option */ #define CMD_GET_OPTION 0x08 /* get an option */ #define CMD_GET_CTIME 0x0d /* get the current song time */ #define CMD_GET_SNAME 0x0f /* get the stream file name */ #define CMD_NEXT 0x10 /* start playing next song if available */ #define CMD_QUIT 0x11 /* shutdown the server */ #define CMD_SEEK 0x12 /* seek in the current stream */ #define CMD_GET_STATE 0x13 /* get the state */ #define CMD_DISCONNECT 0x15 /* disconnect from the server */ #define CMD_GET_BITRATE 0x16 /* get the bitrate */ #define CMD_GET_RATE 0x17 /* get the rate */ #define CMD_GET_CHANNELS 0x18 /* get the number of channels */ #define CMD_PING 0x19 /* request for EV_PONG */ #define CMD_GET_MIXER 0x1a /* get the volume level */ #define CMD_SET_MIXER 0x1b /* set the volume level */ #define CMD_DELETE 0x1c /* delete an item from the playlist */ #define CMD_SEND_PLIST_EVENTS 0x1d /* request for playlist events */ #define CMD_PREV 0x20 /* start playing previous song if available */ #define CMD_SEND_PLIST 0x21 /* send the playlist to the requesting client */ #define CMD_GET_PLIST 0x22 /* get the playlist from one of the clients */ #define CMD_CAN_SEND_PLIST 0x23 /* mark the client as able to send playlist */ #define CMD_CLI_PLIST_ADD 0x24 /* add an item to the client's playlist */ #define CMD_CLI_PLIST_DEL 0x25 /* delete an item from the client's playlist */ #define CMD_CLI_PLIST_CLEAR 0x26 /* clear the client's playlist */ #define CMD_GET_SERIAL 0x27 /* get an unique serial number */ #define CMD_PLIST_SET_SERIAL 0x28 /* assign a serial number to the server's playlist */ #define CMD_LOCK 0x29 /* acquire a lock */ #define CMD_UNLOCK 0x2a /* release the lock */ #define CMD_PLIST_GET_SERIAL 0x2b /* get the serial number of the server's playlist */ #define CMD_GET_TAGS 0x2c /* get tags for the currently played file */ #define CMD_TOGGLE_MIXER_CHANNEL 0x2d /* toggle the mixer channel */ #define CMD_GET_MIXER_CHANNEL_NAME 0x2e /* get the mixer channel's name */ #define CMD_GET_FILE_TAGS 0x2f /* get tags for the specified file */ #define CMD_ABORT_TAGS_REQUESTS 0x30 /* abort previous CMD_GET_FILE_TAGS requests up to some file */ #define CMD_CLI_PLIST_MOVE 0x31 /* move an item */ #define CMD_LIST_MOVE 0x32 /* move an item */ #define CMD_GET_AVG_BITRATE 0x33 /* get the average bitrate */ #define CMD_TOGGLE_SOFTMIXER 0x34 /* toggle use of softmixer */ #define CMD_TOGGLE_EQUALIZER 0x35 /* toggle use of equalizer */ #define CMD_EQUALIZER_REFRESH 0x36 /* refresh EQ-presets */ #define CMD_EQUALIZER_PREV 0x37 /* select previous eq-preset */ #define CMD_EQUALIZER_NEXT 0x38 /* select next eq-preset */ #define CMD_TOGGLE_MAKE_MONO 0x39 /* toggle mono mixing */ #define CMD_JUMP_TO 0x3a /* jumps to a some position in the current stream */ #define CMD_QUEUE_ADD 0x3b /* add an item to the queue */ #define CMD_QUEUE_DEL 0x3c /* delete an item from the queue */ #define CMD_QUEUE_MOVE 0x3d /* move an item in the queue */ #define CMD_QUEUE_CLEAR 0x3e /* clear the queue */ #define CMD_GET_QUEUE 0x3f /* request the queue from the server */ char *socket_name (); int get_int (int sock, int *i); enum noblock_io_status get_int_noblock (int sock, int *i); int send_int (int sock, int i); char *get_str (int sock); int send_str (int sock, const char *str); int get_time (int sock, time_t *i); int send_time (int sock, time_t i); int send_item (int sock, const struct plist_item *item); struct plist_item *recv_item (int sock); struct file_tags *recv_tags (int sock); int send_tags (int sock, const struct file_tags *tags); void event_queue_init (struct event_queue *q); void event_queue_free (struct event_queue *q); void free_event_data (const int type, void *data); struct event *event_get_first (struct event_queue *q); void event_pop (struct event_queue *q); void event_push (struct event_queue *q, const int event, void *data); int event_queue_empty (const struct event_queue *q); enum noblock_io_status event_send_noblock (int sock, struct event_queue *q); void free_tag_ev_data (struct tag_ev_response *d); void free_move_ev_data (struct move_ev_data *m); struct move_ev_data *move_ev_data_dup (const struct move_ev_data *m); struct move_ev_data *recv_move_ev_data (int sock); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/rbtree.c0000664000076400000500000001730412604351677014576 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005 Damian Pietras * * 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. * * Functions based on pseudocode from "Introduction to Algorithms" * The only modification is that we avoid to modify fields of the nil value. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "common.h" #include "rbtree.h" enum rb_color { RB_RED, RB_BLACK }; struct rb_node { struct rb_node *left; struct rb_node *right; struct rb_node *parent; enum rb_color color; const void *data; }; struct rb_tree { struct rb_node *root; /* compare function for two data elements */ rb_t_compare *cmp_fn; /* compare function for data element and a key value */ rb_t_compare_key *cmp_key_fn; /* pointer to additional data passed to compare functions */ const void *adata; }; /* item used as a null value */ static struct rb_node rb_null = { NULL, NULL, NULL, RB_BLACK, NULL }; static void rb_left_rotate (struct rb_node **root, struct rb_node *x) { struct rb_node *y = x->right; assert (y != &rb_null); x->right = y->left; if (y->left != &rb_null) y->left->parent = x; y->parent = x->parent; if (x->parent == &rb_null) *root = y; else { if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; } y->left = x; x->parent = y; } static void rb_right_rotate (struct rb_node **root, struct rb_node *x) { struct rb_node *y = x->left; assert (y != &rb_null); x->left = y->right; if (y->right != &rb_null) y->right->parent = x; y->parent = x->parent; if (x->parent == &rb_null) *root = y; else { if (x == x->parent->right) x->parent->right = y; else x->parent->left = y; } y->right = x; x->parent = y; } static void rb_insert_fixup (struct rb_node **root, struct rb_node *z) { while (z->parent->color == RB_RED) if (z->parent == z->parent->parent->left) { struct rb_node *y = z->parent->parent->right; if (y->color == RB_RED) { z->parent->color = RB_BLACK; y->color = RB_BLACK; z->parent->parent->color = RB_RED; z = z->parent->parent; } else { if (z == z->parent->right) { z = z->parent; rb_left_rotate (root, z); } z->parent->color = RB_BLACK; z->parent->parent->color = RB_RED; rb_right_rotate (root, z->parent->parent); } } else { struct rb_node *y = z->parent->parent->left; if (y->color == RB_RED) { z->parent->color = RB_BLACK; y->color = RB_BLACK; z->parent->parent->color = RB_RED; z = z->parent->parent; } else { if (z == z->parent->left) { z = z->parent; rb_right_rotate (root, z); } z->parent->color = RB_BLACK; z->parent->parent->color = RB_RED; rb_left_rotate (root, z->parent->parent); } } (*root)->color = RB_BLACK; } static void rb_delete_fixup (struct rb_node **root, struct rb_node *x, struct rb_node *parent) { struct rb_node *w; while (x != *root && x->color == RB_BLACK) { if (x == parent->left) { w = parent->right; if (w->color == RB_RED) { w->color = RB_BLACK; parent->color = RB_RED; rb_left_rotate (root, parent); w = parent->right; } if (w->left->color == RB_BLACK && w->right->color == RB_BLACK) { w->color = RB_RED; x = parent; parent = x->parent; } else { if (w->right->color == RB_BLACK) { w->left->color = RB_BLACK; w->color = RB_RED; rb_right_rotate (root, w); w = parent->right; } w->color = parent->color; parent->color = RB_BLACK; w->right->color = RB_BLACK; rb_left_rotate (root, parent); x = *root; } } else { w = parent->left; if (w->color == RB_RED) { w->color = RB_BLACK; parent->color = RB_RED; rb_right_rotate (root, parent); w = parent->left; } if (w->right->color == RB_BLACK && w->left->color == RB_BLACK) { w->color = RB_RED; x = parent; parent = x->parent; } else { if (w->left->color == RB_BLACK) { w->right->color = RB_BLACK; w->color = RB_RED; rb_left_rotate (root, w); w = parent->left; } w->color = parent->color; parent->color = RB_BLACK; w->left->color = RB_BLACK; rb_right_rotate (root, parent); x = *root; } } } x->color = RB_BLACK; } void rb_insert (struct rb_tree *t, void *data) { struct rb_node *x, *y, *z; assert (t != NULL); assert (t->root != NULL); x = t->root; y = &rb_null; z = (struct rb_node *)xmalloc (sizeof(struct rb_node)); z->data = data; while (x != &rb_null) { int cmp = t->cmp_fn (z->data, x->data, t->adata); y = x; if (cmp < 0) x = x->left; else if (cmp > 0) x = x->right; else abort (); } z->parent = y; if (y == &rb_null) t->root = z; else { if (t->cmp_fn (z->data, y->data, t->adata) < 0) y->left = z; else y->right = z; } z->left = &rb_null; z->right = &rb_null; z->color = RB_RED; rb_insert_fixup (&t->root, z); } struct rb_node *rb_search (struct rb_tree *t, const void *key) { struct rb_node *x; assert (t != NULL); assert (t->root != NULL); assert (key != NULL); x = t->root; while (x != &rb_null) { int cmp = t->cmp_key_fn (key, x->data, t->adata); if (cmp < 0) x = x->left; else if (cmp > 0) x = x->right; else break; } return x; } int rb_is_null (const struct rb_node *n) { return n == &rb_null; } const void *rb_get_data (const struct rb_node *n) { return n->data; } void rb_set_data (struct rb_node *n, const void *data) { n->data = data; } static struct rb_node *rb_min_internal (struct rb_node *n) { if (n == &rb_null) return &rb_null; while (n->left != &rb_null) n = n->left; return n; } struct rb_node *rb_min (struct rb_tree *t) { assert (t != NULL); assert (t->root != NULL); return rb_min_internal (t->root); } struct rb_node *rb_next (struct rb_node *x) { struct rb_node *y; if (x->right != &rb_null) return rb_min_internal (x->right); y = x->parent; while (y != &rb_null && x == y->right) { x = y; y = y->parent; } return y; } void rb_delete (struct rb_tree *t, const void *key) { struct rb_node *z; assert (t != NULL); assert (t->root != NULL); assert (key != NULL); z = rb_search (t, key); if (z != &rb_null) { struct rb_node *x, *y, *parent; if (z->left == &rb_null || z->right == &rb_null) y = z; else y = rb_next (z); if (y->left != &rb_null) x = y->left; else x = y->right; parent = y->parent; if (x != &rb_null) x->parent = parent; if (y->parent == &rb_null) t->root = x; else { if (y == y->parent->left) y->parent->left = x; else y->parent->right = x; } if (y != z) z->data = y->data; if (y->color == RB_BLACK) rb_delete_fixup (&t->root, x, parent); free (y); } } struct rb_tree *rb_tree_new (rb_t_compare *cmp_fn, rb_t_compare_key *cmp_key_fn, const void *adata) { struct rb_tree *t; assert (cmp_fn != NULL); assert (cmp_key_fn != NULL); t = xmalloc (sizeof (*t)); t->root = &rb_null; t->cmp_fn = cmp_fn; t->cmp_key_fn = cmp_key_fn; t->adata = adata; return t; } static void rb_destroy (struct rb_node *n) { if (n != &rb_null) { rb_destroy (n->right); rb_destroy (n->left); free (n); } } void rb_tree_clear (struct rb_tree *t) { assert (t != NULL); assert (t->root != NULL); if (t->root != &rb_null) { rb_destroy (t->root->left); rb_destroy (t->root->right); free (t->root); t->root = &rb_null; } } void rb_tree_free (struct rb_tree *t) { assert (t != NULL); assert (t->root != NULL); rb_tree_clear (t); free (t); } moc-2.6.0~svn-r3005/rbtree.h0000664000076400000500000000174212424322506014567 0ustar riesebiesrc#ifndef RBTREE_H #define RBTREE_H #ifdef __cplusplus extern "C" { #endif typedef int rb_t_compare (const void *, const void *, const void *); typedef int rb_t_compare_key (const void *, const void *, const void *); struct rb_tree; struct rb_node; /* Whole-of-tree functions. */ struct rb_tree *rb_tree_new (rb_t_compare *cmp_fn, rb_t_compare_key *cmp_key_fn, const void *adata); void rb_tree_clear (struct rb_tree *t); void rb_tree_free (struct rb_tree *t); /* Individual node functions. */ void rb_delete (struct rb_tree *t, const void *key); struct rb_node *rb_next (struct rb_node *x); struct rb_node *rb_min (struct rb_tree *t); int rb_is_null (const struct rb_node *n); const void *rb_get_data (const struct rb_node *n); void rb_set_data (struct rb_node *n, const void *data); struct rb_node *rb_search (struct rb_tree *t, const void *key); void rb_insert (struct rb_tree *t, void *data); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/rcc.c0000664000076400000500000000272611776160731014062 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2005,2011 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_RCC # include #endif #include #include "rcc.h" char *rcc_reencode (char *str) { char *result = str; assert (str != NULL); #ifdef HAVE_RCC rcc_string rccstring; rccstring = rccFrom (NULL, 0, str); if (rccstring) { if (*rccstring) { char *reencoded; reencoded = rccToCharset (NULL, "UTF-8", rccstring); if (reencoded) { free (result); result = reencoded; } } free (rccstring); } #endif /* HAVE_RCC */ return result; } void rcc_init () { #ifdef HAVE_RCC rcc_class classes[] = { {"input", RCC_CLASS_STANDARD, NULL, NULL, "Input Encoding", 0}, {"output", RCC_CLASS_KNOWN, NULL, NULL, "Output Encoding", 0}, {NULL, 0, NULL, NULL, NULL, 0} }; rccInit (); rccInitDefaultContext (NULL, 0, 0, classes, 0); rccLoad (NULL, "moc"); rccSetOption (NULL, RCC_OPTION_TRANSLATE, RCC_OPTION_TRANSLATE_SKIP_PARRENT); rccSetOption (NULL, RCC_OPTION_AUTODETECT_LANGUAGE, 1); #endif /* HAVE_RCC */ } void rcc_cleanup () { #ifdef HAVE_RCC rccFree (); #endif /* HAVE_RCC */ } moc-2.6.0~svn-r3005/rcc.h0000664000076400000500000000025611776160731014063 0ustar riesebiesrc#ifndef RCC_H #define RCC_H #ifdef __cplusplus extern "C" { #endif char *rcc_reencode (char *); void rcc_init (); void rcc_cleanup (); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/server.c0000664000076400000500000011621713333136304014610 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2003 - 2005 Damian Pietras * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifdef HAVE_GETRLIMIT # include #endif #include #include #include #include #include #include #include #include #include #include #include #define DEBUG #include "common.h" #include "log.h" #include "protocol.h" #include "audio.h" #include "oss.h" #include "options.h" #include "server.h" #include "playlist.h" #include "tags_cache.h" #include "files.h" #include "softmixer.h" #include "equalizer.h" #define SERVER_LOG "mocp_server_log" #define PID_FILE "pid" struct client { int socket; /* -1 if inactive */ int wants_plist_events; /* requested playlist events? */ struct event_queue events; pthread_mutex_t events_mtx; int requests_plist; /* is the client waiting for the playlist? */ int can_send_plist; /* can this client send a playlist? */ int lock; /* is this client locking us? */ int serial; /* used for generating unique serial numbers */ }; static struct client clients[CLIENTS_MAX]; /* Thread ID of the server thread. */ static pthread_t server_tid; /* Pipe used to wake up the server from select() from another thread. */ static int wake_up_pipe[2]; /* Socket used to accept incoming client connections. */ static int server_sock = -1; /* Set to 1 when a signal arrived causing the program to exit. */ static volatile int server_quit = 0; /* Information about currently played file */ static struct { int avg_bitrate; int bitrate; int rate; int channels; } sound_info = { -1, -1, -1, -1 }; static struct tags_cache *tags_cache; extern char **environ; static void write_pid_file () { char *fname = create_file_name (PID_FILE); FILE *file; if ((file = fopen(fname, "w")) == NULL) fatal ("Can't open pid file for writing: %s", xstrerror (errno)); fprintf (file, "%d\n", getpid()); fclose (file); } /* Check if there is a pid file and if it is valid, return the pid, else 0 */ static pid_t check_pid_file () { FILE *file; pid_t pid; char *fname = create_file_name (PID_FILE); /* Read the pid file */ if ((file = fopen(fname, "r")) == NULL) return 0; if (fscanf(file, "%d", &pid) != 1) { fclose (file); return 0; } fclose (file); return pid; } static void sig_chld (int sig LOGIT_ONLY) { int saved_errno; pid_t rc; log_signal (sig); saved_errno = errno; do { rc = waitpid (-1, NULL, WNOHANG); } while (rc > 0); errno = saved_errno; } static void sig_exit (int sig) { log_signal (sig); server_quit = 1; // FIXME (JCF): pthread_*() are not async-signal-safe and // should not be used within signal handlers. if (!pthread_equal (server_tid, pthread_self())) pthread_kill (server_tid, sig); } static void clients_init () { int i; for (i = 0; i < CLIENTS_MAX; i++) { clients[i].socket = -1; pthread_mutex_init (&clients[i].events_mtx, NULL); } } static void clients_cleanup () { int i, rc; for (i = 0; i < CLIENTS_MAX; i++) { clients[i].socket = -1; rc = pthread_mutex_destroy (&clients[i].events_mtx); if (rc != 0) log_errno ("Can't destroy events mutex", rc); } } /* Add a client to the list, return 1 if ok, 0 on error (max clients exceeded) */ static int add_client (int sock) { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket == -1) { clients[i].wants_plist_events = 0; LOCK (clients[i].events_mtx); event_queue_free (&clients[i].events); event_queue_init (&clients[i].events); UNLOCK (clients[i].events_mtx); clients[i].socket = sock; clients[i].requests_plist = 0; clients[i].can_send_plist = 0; clients[i].lock = 0; tags_cache_clear_queue (tags_cache, i); return 1; } return 0; } /* Return index of a client that has a lock acquired. Return -1 if there is no * lock. */ static int locking_client () { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1 && clients[i].lock) return i; return -1; } /* Acquire a lock for this client. Return 0 on error. */ static int client_lock (struct client *cli) { if (cli->lock) { logit ("Client wants deadlock"); return 0; } assert (locking_client() == -1); cli->lock = 1; logit ("Lock acquired for client with fd %d", cli->socket); return 1; } /* Return != 0 if this client holds a lock. */ static int is_locking (const struct client *cli) { return cli->lock; } /* Release the lock hold by the client. Return 0 on error. */ static int client_unlock (struct client *cli) { if (!cli->lock) { logit ("Client wants to unlock when there is no lock"); return 0; } cli->lock = 0; logit ("Lock released by client with fd %d", cli->socket); return 1; } /* Return the client index from the clients table. */ static int client_index (const struct client *cli) { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket == cli->socket) return i; return -1; } static void del_client (struct client *cli) { cli->socket = -1; LOCK (cli->events_mtx); event_queue_free (&cli->events); tags_cache_clear_queue (tags_cache, client_index(cli)); UNLOCK (cli->events_mtx); } /* Check if the process with given PID exists. Return != 0 if so. */ static int valid_pid (const pid_t pid) { return kill(pid, 0) == 0 ? 1 : 0; } static void wake_up_server () { int w = 1; debug ("Waking up the server"); if (write(wake_up_pipe[1], &w, sizeof(w)) < 0) log_errno ("Can't wake up the server: (write() failed)", errno); } static void redirect_output (FILE *stream) { FILE *rc; if (stream == stdin) rc = freopen ("/dev/null", "r", stream); else rc = freopen ("/dev/null", "w", stream); if (!rc) fatal ("Can't open /dev/null: %s", xstrerror (errno)); } static void log_process_stack_size () { #if !defined(NDEBUG) && defined(HAVE_GETRLIMIT) int rc; struct rlimit limits; rc = getrlimit (RLIMIT_STACK, &limits); if (rc == 0) logit ("Process's stack size: %u", (unsigned int)limits.rlim_cur); #endif } static void log_pthread_stack_size () { #if !defined(NDEBUG) && defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) int rc; size_t stack_size; pthread_attr_t attr; rc = pthread_attr_init (&attr); if (rc) return; rc = pthread_attr_getstacksize (&attr, &stack_size); if (rc == 0) logit ("PThread's stack size: %u", (unsigned int)stack_size); pthread_attr_destroy (&attr); #endif } /* Handle running external command on requested event. */ static void run_extern_cmd (const char *event) { char *command; command = xstrdup (options_get_str (event)); if (command) { char *args[2], *err; args[0] = xstrdup (command); args[1] = NULL; switch (fork ()) { case 0: execve (command, args, environ); fatal ("Error when running %s command '%s': %s", event, command, xstrerror (errno)); case -1: err = xstrerror (errno); logit ("Error when running %s command '%s': %s", event, command, err); free (err); break; } free (command); free (args[0]); } } /* Initialize the server - return fd of the listening socket or -1 on error */ void server_init (int debugging, int foreground) { struct sockaddr_un sock_name; pid_t pid; logit ("Starting MOC Server"); assert (server_sock == -1); pid = check_pid_file (); if (pid && valid_pid(pid)) { fprintf (stderr, "\nIt seems that the server is already running" " with pid %d.\n", pid); fprintf (stderr, "If it is not true, remove the pid file (%s)" " and try again.\n", create_file_name(PID_FILE)); fatal ("Exiting!"); } if (foreground) log_init_stream (stdout, "stdout"); else { FILE *logfp; logfp = NULL; if (debugging) { logfp = fopen (SERVER_LOG, "a"); if (!logfp) fatal ("Can't open server log file: %s", xstrerror (errno)); } log_init_stream (logfp, SERVER_LOG); } if (pipe(wake_up_pipe) < 0) fatal ("pipe() failed: %s", xstrerror (errno)); unlink (socket_name()); /* Create a socket. * For reasons why AF_UNIX is the correct constant to use in both * cases, see the commentary the SVN log for commit r9999. */ server_sock = socket (AF_UNIX, SOCK_STREAM, 0); if (server_sock == -1) fatal ("Can't create socket: %s", xstrerror (errno)); sock_name.sun_family = AF_UNIX; strcpy (sock_name.sun_path, socket_name()); /* Bind to socket */ if (bind(server_sock, (struct sockaddr *)&sock_name, SUN_LEN(&sock_name)) == -1) fatal ("Can't bind() to the socket: %s", xstrerror (errno)); if (listen(server_sock, 1) == -1) fatal ("listen() failed: %s", xstrerror (errno)); /* Log stack sizes so stack overflows can be debugged. */ log_process_stack_size (); log_pthread_stack_size (); clients_init (); audio_initialize (); tags_cache = tags_cache_new (options_get_int("TagsCacheSize")); tags_cache_load (tags_cache, create_file_name("cache")); server_tid = pthread_self (); xsignal (SIGTERM, sig_exit); xsignal (SIGINT, foreground ? sig_exit : SIG_IGN); xsignal (SIGHUP, SIG_IGN); xsignal (SIGQUIT, sig_exit); xsignal (SIGPIPE, SIG_IGN); xsignal (SIGCHLD, sig_chld); write_pid_file (); if (!foreground) { setsid (); redirect_output (stdin); redirect_output (stdout); redirect_output (stderr); } logit ("Running OnServerStart"); run_extern_cmd ("OnServerStart"); return; } /* Send EV_DATA and the integer value. Return 0 on error. */ static int send_data_int (const struct client *cli, const int data) { assert (cli->socket != -1); if (!send_int(cli->socket, EV_DATA) || !send_int(cli->socket, data)) return 0; return 1; } /* Send EV_DATA and the boolean value. Return 0 on error. */ static int send_data_bool (const struct client *cli, const bool data) { assert (cli->socket != -1); if (!send_int(cli->socket, EV_DATA) || !send_int(cli->socket, data ? 1 : 0)) return 0; return 1; } /* Send EV_DATA and the string value. Return 0 on error. */ static int send_data_str (const struct client *cli, const char *str) { if (!send_int(cli->socket, EV_DATA) || !send_str(cli->socket, str)) return 0; return 1; } /* Add event to the client's queue */ static void add_event (struct client *cli, const int event, void *data) { LOCK (cli->events_mtx); event_push (&cli->events, event, data); UNLOCK (cli->events_mtx); } static void on_song_change () { static char *last_file = NULL; static lists_t_strs *on_song_change = NULL; int ix; bool same_file, unpaused; char *curr_file, **args; struct file_tags *curr_tags; lists_t_strs *arg_list; /* We only need to do OnSongChange tokenisation once. */ if (on_song_change == NULL) { char *command; on_song_change = lists_strs_new (4); command = options_get_str ("OnSongChange"); if (command) lists_strs_tokenise (on_song_change, command); } if (lists_strs_empty (on_song_change)) return; curr_file = audio_get_sname (); if (curr_file == NULL) return; same_file = (last_file && !strcmp (last_file, curr_file)); unpaused = (audio_get_prev_state () == STATE_PAUSE); if (same_file && (unpaused || !options_get_bool ("RepeatSongChange"))) { free (curr_file); return; } curr_tags = tags_cache_get_immediate (tags_cache, curr_file, TAGS_COMMENTS | TAGS_TIME); arg_list = lists_strs_new (lists_strs_size (on_song_change)); for (ix = 0; ix < lists_strs_size (on_song_change); ix += 1) { char *arg, *str; arg = lists_strs_at (on_song_change, ix); if (arg[0] != '%') lists_strs_append (arg_list, arg); else if (!curr_tags) lists_strs_append (arg_list, ""); else { switch (arg[1]) { case 'a': str = curr_tags->artist ? curr_tags->artist : ""; lists_strs_append (arg_list, str); break; case 'r': str = curr_tags->album ? curr_tags->album : ""; lists_strs_append (arg_list, str); break; case 't': str = curr_tags->title ? curr_tags->title : ""; lists_strs_append (arg_list, str); break; case 'n': if (curr_tags->track >= 0) { str = (char *) xmalloc (sizeof (char) * 16); snprintf (str, 16, "%d", curr_tags->track); lists_strs_push (arg_list, str); } else lists_strs_append (arg_list, ""); break; case 'f': lists_strs_append (arg_list, curr_file); break; case 'D': if (curr_tags->time >= 0) { str = (char *) xmalloc (sizeof (char) * 16); snprintf (str, 16, "%d", curr_tags->time); lists_strs_push (arg_list, str); } else lists_strs_append (arg_list, ""); break; case 'd': if (curr_tags->time >= 0) { str = (char *) xmalloc (sizeof (char) * 32); sec_to_min (str, curr_tags->time); lists_strs_push (arg_list, str); } else lists_strs_append (arg_list, ""); break; default: lists_strs_append (arg_list, arg); } } } tags_free (curr_tags); #ifndef NDEBUG { char *cmd; cmd = lists_strs_fmt (arg_list, " %s"); debug ("Running command: %s", cmd); free (cmd); } #endif switch (fork ()) { case 0: args = lists_strs_save (arg_list); execve (args[0], args, environ); fatal ("Error when running OnSongChange command '%s': %s", args[0], xstrerror (errno)); case -1: log_errno ("Failed to fork()", errno); } lists_strs_free (arg_list); free (last_file); last_file = curr_file; } /* Return true iff 'event' is a playlist event. */ static inline bool is_plist_event (const int event) { bool result = false; switch (event) { case EV_PLIST_ADD: case EV_PLIST_DEL: case EV_PLIST_MOVE: case EV_PLIST_CLEAR: result = true; } return result; } static void add_event_all (const int event, const void *data) { int i; int added = 0; if (event == EV_STATE) { switch (audio_get_state()) { case STATE_PLAY: on_song_change (); break; case STATE_STOP: run_extern_cmd ("OnStop"); break; } } for (i = 0; i < CLIENTS_MAX; i++) { void *data_copy = NULL; if (clients[i].socket == -1) continue; if (!clients[i].wants_plist_events && is_plist_event (event)) continue; if (data) { if (event == EV_PLIST_ADD || event == EV_QUEUE_ADD) { data_copy = plist_new_item (); plist_item_copy (data_copy, data); } else if (event == EV_PLIST_DEL || event == EV_QUEUE_DEL || event == EV_STATUS_MSG || event == EV_SRV_ERROR) { data_copy = xstrdup (data); } else if (event == EV_PLIST_MOVE || event == EV_QUEUE_MOVE) data_copy = move_ev_data_dup ( (struct move_ev_data *) data); else logit ("Unhandled data!"); } add_event (&clients[i], event, data_copy); added++; } if (added) wake_up_server (); else debug ("No events have been added because there are no clients"); } /* Send events from the queue. Return 0 on error. */ static int flush_events (struct client *cli) { enum noblock_io_status st = NB_IO_OK; LOCK (cli->events_mtx); while (!event_queue_empty(&cli->events) && (st = event_send_noblock(cli->socket, &cli->events)) == NB_IO_OK) ; UNLOCK (cli->events_mtx); return st != NB_IO_ERR ? 1 : 0; } /* Send events to clients whose sockets are ready to write. */ static void send_events (fd_set *fds) { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1 && FD_ISSET(clients[i].socket, fds)) { debug ("Flushing events for client %d", i); if (!flush_events (&clients[i])) { close (clients[i].socket); del_client (&clients[i]); } } } /* End playing and cleanup. */ static void server_shutdown () { logit ("Server exiting..."); audio_exit (); tags_cache_free (tags_cache); tags_cache = NULL; logit ("Running OnServerStop"); run_extern_cmd ("OnServerStop"); unlink (socket_name()); unlink (create_file_name(PID_FILE)); close (wake_up_pipe[0]); close (wake_up_pipe[1]); logit ("Server exited"); log_close (); } /* Send EV_BUSY message and close the connection. */ static void busy (int sock) { logit ("Closing connection due to maximum number of clients reached"); send_int (sock, EV_BUSY); close (sock); } /* Handle CMD_LIST_ADD, return 1 if ok or 0 on error. */ static int req_list_add (struct client *cli) { char *file; file = get_str (cli->socket); if (!file) return 0; logit ("Adding '%s' to the list", file); audio_plist_add (file); free (file); return 1; } /* Handle CMD_QUEUE_ADD, return 1 if ok or 0 on error. */ static int req_queue_add (const struct client *cli) { char *file; struct plist_item *item; file = get_str (cli->socket); if (!file) return 0; logit ("Adding '%s' to the queue", file); audio_queue_add (file); /* Wrap the filename in struct plist_item. * We don't need tags, because the player gets them * when playing the file. This may change if there is * support for viewing/reordering the queue and here * is the place to read the tags and fill them into * the item. */ item = plist_new_item (); item->file = xstrdup (file); item->type = file_type (file); item->mtime = get_mtime (file); add_event_all (EV_QUEUE_ADD, item); plist_free_item_fields (item); free (item); free (file); return 1; } /* Handle CMD_PLAY, return 1 if ok or 0 on error. */ static int req_play (struct client *cli) { char *file; if (!(file = get_str(cli->socket))) return 0; logit ("Playing %s", *file ? file : "first element on the list"); audio_play (file); free (file); return 1; } /* Handle CMD_SEEK, return 1 if ok or 0 on error */ static int req_seek (struct client *cli) { int sec; if (!get_int(cli->socket, &sec)) return 0; logit ("Seeking %ds", sec); audio_seek (sec); return 1; } /* Handle CMD_JUMP_TO, return 1 if ok or 0 on error */ static int req_jump_to (struct client *cli) { int sec; if (!get_int(cli->socket, &sec)) return 0; logit ("Jumping to %ds", sec); audio_jump_to (sec); return 1; } /* Report an error logging it and sending a message to the client. */ void server_error (const char *file, int line, const char *function, const char *msg) { internal_logit (file, line, function, "ERROR: %s", msg); add_event_all (EV_SRV_ERROR, msg); } /* Send the song name to the client. Return 0 on error. */ static int send_sname (struct client *cli) { int status = 1; char *sname = audio_get_sname (); if (!send_data_str(cli, sname ? sname : "")) status = 0; free (sname); return status; } /* Return 0 if an option is valid when getting/setting with the client. */ static int valid_sync_option (const char *name) { return !strcasecmp(name, "ShowStreamErrors") || !strcasecmp(name, "Repeat") || !strcasecmp(name, "Shuffle") || !strcasecmp(name, "AutoNext"); } /* Send requested option value to the client. Return 1 if OK. */ static int send_option (struct client *cli) { char *name; if (!(name = get_str(cli->socket))) return 0; /* We can send only a few options, others make no sense here. */ if (!valid_sync_option(name)) { logit ("Client wanted to get invalid option '%s'", name); free (name); return 0; } /* All supported options are boolean type. */ if (!send_data_bool(cli, options_get_bool(name))) { free (name); return 0; } free (name); return 1; } /* Get and set an option from the client. Return 1 on error. */ static int get_set_option (struct client *cli) { char *name; int val; if (!(name = get_str (cli->socket))) return 0; if (!valid_sync_option (name)) { logit ("Client requested setting invalid option '%s'", name); return 0; } if (!get_int (cli->socket, &val)) { free (name); return 0; } options_set_bool (name, val ? true : false); free (name); add_event_all (EV_OPTIONS, NULL); return 1; } /* Set the mixer to the value provided by the client. Return 0 on error. */ static int set_mixer (struct client *cli) { int val; if (!get_int(cli->socket, &val)) return 0; audio_set_mixer (val); return 1; } /* Delete an item from the playlist. Return 0 on error. */ static int delete_item (struct client *cli) { char *file; if (!(file = get_str(cli->socket))) return 0; debug ("Request for deleting %s", file); audio_plist_delete (file); free (file); return 1; } static int req_queue_del (const struct client *cli) { char *file; if (!(file = get_str(cli->socket))) return 0; debug ("Deleting '%s' from queue", file); audio_queue_delete (file); add_event_all (EV_QUEUE_DEL, file); free (file); return 1; } /* Return the index of the first client able to send the playlist or -1 if * there isn't any. */ static int find_sending_plist () { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1 && clients[i].can_send_plist) return i; return -1; } /* Handle CMD_GET_PLIST. Return 0 on error. */ static int get_client_plist (struct client *cli) { int first; debug ("Client with fd %d requests the playlist", cli->socket); /* Find the first connected client, and ask it to send the playlist. * Here, send 1 if there is a client with the playlist, or 0 if there * isn't. */ cli->requests_plist = 1; first = find_sending_plist (); if (first == -1) { debug ("No clients with the playlist"); cli->requests_plist = 0; if (!send_data_int(cli, 0)) return 0; return 1; } if (!send_data_int(cli, 1)) return 0; if (!send_int(clients[first].socket, EV_SEND_PLIST)) return 0; return 1; } /* Find the client requesting the playlist. */ static int find_cli_requesting_plist () { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].requests_plist) return i; return -1; } /* Handle CMD_SEND_PLIST. Some client requested to get the playlist, so we asked * another client to send it (EV_SEND_PLIST). */ static int req_send_plist (struct client *cli) { int requesting = find_cli_requesting_plist (); int send_fd; struct plist_item *item; int serial; debug ("Client with fd %d wants to send its playlists", cli->socket); if (requesting == -1) { logit ("No clients are requesting the playlist"); send_fd = -1; } else { send_fd = clients[requesting].socket; if (!send_int(send_fd, EV_DATA)) { logit ("Error while sending response; disconnecting the client"); close (send_fd); del_client (&clients[requesting]); send_fd = -1; } } if (!get_int(cli->socket, &serial)) { logit ("Error while getting serial"); return 0; } if (send_fd != -1 && !send_int(send_fd, serial)) { error ("Error while sending serial; disconnecting the client"); close (send_fd); del_client (&clients[requesting]); send_fd = -1; } /* Even if no clients are requesting the playlist, we must read it, * because there is no way to say that we don't need it. */ while ((item = recv_item(cli->socket)) && item->file[0]) { if (send_fd != -1 && !send_item(send_fd, item)) { logit ("Error while sending item; disconnecting the client"); close (send_fd); del_client (&clients[requesting]); send_fd = -1; } plist_free_item_fields (item); free (item); } if (item) { plist_free_item_fields (item); free (item); logit ("Playlist sent"); } else logit ("Error while receiving item"); if (send_fd != -1 && !send_item (send_fd, NULL)) { logit ("Error while sending end of playlist mark; " "disconnecting the client"); close (send_fd); del_client (&clients[requesting]); return 0; } if (requesting != -1) clients[requesting].requests_plist = 0; return item ? 1 : 0; } /* Client requested we send the queue so we get it from audio.c and * send it to the client. */ static int req_send_queue (struct client *cli) { int i; struct plist *queue; logit ("Client with fd %d wants queue... sending it", cli->socket); if (!send_int(cli->socket, EV_DATA)) { logit ("Error while sending response; disconnecting the client"); close (cli->socket); del_client (cli); return 0; } queue = audio_queue_get_contents (); for (i = 0; i < queue->num; i++) if (!plist_deleted(queue, i)) { if(!send_item(cli->socket, &queue->items[i])){ logit ("Error sending queue; disconnecting the client"); close (cli->socket); del_client (cli); free (queue); return 0; } } plist_free (queue); free (queue); if (!send_item (cli->socket, NULL)) { logit ("Error while sending end of playlist mark; " "disconnecting the client"); close (cli->socket); del_client (cli); return 0; } logit ("Queue sent"); return 1; } /* Handle command that synchronises the playlists between interfaces * (except forwarding the whole list). Return 0 on error. */ static int plist_sync_cmd (struct client *cli, const int cmd) { if (cmd == CMD_CLI_PLIST_ADD) { struct plist_item *item; debug ("Sending EV_PLIST_ADD"); if (!(item = recv_item(cli->socket))) { logit ("Error while receiving item"); return 0; } add_event_all (EV_PLIST_ADD, item); plist_free_item_fields (item); free (item); } else if (cmd == CMD_CLI_PLIST_DEL) { char *file; debug ("Sending EV_PLIST_DEL"); if (!(file = get_str(cli->socket))) { logit ("Error while receiving file"); return 0; } add_event_all (EV_PLIST_DEL, file); free (file); } else if (cmd == CMD_CLI_PLIST_MOVE) { struct move_ev_data m; if (!(m.from = get_str(cli->socket)) || !(m.to = get_str(cli->socket))) { logit ("Error while receiving file"); return 0; } add_event_all (EV_PLIST_MOVE, &m); free (m.from); free (m.to); } else { /* it can be only CMD_CLI_PLIST_CLEAR */ debug ("Sending EV_PLIST_CLEAR"); add_event_all (EV_PLIST_CLEAR, NULL); } return 1; } /* Handle CMD_PLIST_GET_SERIAL. Return 0 on error. */ static int req_plist_get_serial (struct client *cli) { if (!send_data_int(cli, audio_plist_get_serial())) return 0; return 1; } /* Handle CMD_PLIST_SET_SERIAL. Return 0 on error. */ static int req_plist_set_serial (struct client *cli) { int serial; if (!get_int(cli->socket, &serial)) return 0; if (serial < 0) { logit ("Client wants to set bad serial number"); return 0; } debug ("Setting the playlist serial number to %d", serial); audio_plist_set_serial (serial); return 1; } /* Generate a unique playlist serial number. */ static int gen_serial (const struct client *cli) { static int seed = 0; int serial; /* Each client must always get a different serial number, so we use * also the client index to generate it. It must also not be used by * our playlist to not confuse clients. * There can be 256 different serial number per client, but it's * enough since clients use only two playlists. */ do { serial = (seed << 8) | client_index(cli); seed = (seed + 1) & 0xFF; } while (serial == audio_plist_get_serial()); debug ("Generated serial %d for client with fd %d", serial, cli->socket); return serial; } /* Send the unique number to the client. Return 0 on error. */ static int send_serial (struct client *cli) { if (!send_data_int(cli, gen_serial(cli))) { logit ("Error when sending serial"); return 0; } return 1; } /* Send tags to the client. Return 0 on error. */ static int req_get_tags (struct client *cli) { struct file_tags *tags; int res = 1; debug ("Sending tags to client with fd %d...", cli->socket); if (!send_int(cli->socket, EV_DATA)) { logit ("Error when sending EV_DATA"); return 0; } tags = audio_get_curr_tags (); if (!send_tags(cli->socket, tags)) { logit ("Error when sending tags"); res = 0; } if (tags) tags_free (tags); return res; } /* Handle CMD_GET_MIXER_CHANNEL_NAME. Return 0 on error. */ int req_get_mixer_channel_name (struct client *cli) { int status = 1; char *name = audio_get_mixer_channel_name (); if (!send_data_str(cli, name ? name : "")) status = 0; free (name); return status; } /* Handle CMD_TOGGLE_MIXER_CHANNEL. */ void req_toggle_mixer_channel () { audio_toggle_mixer_channel (); add_event_all (EV_MIXER_CHANGE, NULL); } /* Handle CMD_TOGGLE_SOFTMIXER. */ void req_toggle_softmixer () { softmixer_set_active(!softmixer_is_active()); add_event_all (EV_MIXER_CHANGE, NULL); } void update_eq_name() { char buffer[27]; char *n = equalizer_current_eqname(); int l = strlen(n); /* Status message can only take strings up to 25 chars * (Without terminating zero). * The message header has 11 chars (EQ set to...). */ if (l > 14) { n[14] = 0; n[13] = '.'; n[12] = '.'; n[11] = '.'; } sprintf(buffer, "EQ set to: %s", n); logit("%s", buffer); free(n); status_msg(buffer); } void req_toggle_equalizer () { equalizer_set_active(!equalizer_is_active()); update_eq_name(); } void req_equalizer_refresh() { equalizer_refresh(); status_msg("Equalizer refreshed"); logit("Equalizer refreshed"); } void req_equalizer_prev() { equalizer_prev(); update_eq_name(); } void req_equalizer_next() { equalizer_next(); update_eq_name(); } void req_toggle_make_mono() { char buffer[128]; softmixer_set_mono(!softmixer_is_mono()); sprintf(buffer, "Mono-Mixing set to: %s", softmixer_is_mono()?"on":"off"); status_msg(buffer); } /* Handle CMD_GET_FILE_TAGS. Return 0 on error. */ static int get_file_tags (const int cli_id) { char *file; int tags_sel; if (!(file = get_str(clients[cli_id].socket))) return 0; if (!get_int(clients[cli_id].socket, &tags_sel)) { free (file); return 0; } tags_cache_add_request (tags_cache, file, tags_sel, cli_id); free (file); return 1; } static int abort_tags_requests (const int cli_id) { char *file; if (!(file = get_str(clients[cli_id].socket))) return 0; tags_cache_clear_up_to (tags_cache, file, cli_id); free (file); return 1; } /* Handle CMD_LIST_MOVE. Return 0 on error. */ static int req_list_move (struct client *cli) { char *from; char *to; if (!(from = get_str(cli->socket))) return 0; if (!(to = get_str(cli->socket))) { free (from); return 0; } audio_plist_move (from, to); free (from); free (to); return 1; } /* Handle CMD_QUEUE_MOVE. Return 0 on error. */ static int req_queue_move (const struct client *cli) { struct move_ev_data m; if (!(m.from = get_str(cli->socket))) return 0; if (!(m.to = get_str(cli->socket))) { free (m.from); return 0; } audio_queue_move (m.from, m.to); logit ("Swapping %s with %s in the queue", m.from, m.to); /* Broadcast the event to clients */ add_event_all (EV_QUEUE_MOVE, &m); free (m.from); free (m.to); return 1; } /* Receive a command from the client and execute it. */ static void handle_command (const int client_id) { int cmd; int err = 0; struct client *cli = &clients[client_id]; if (!get_int(cli->socket, &cmd)) { logit ("Failed to get command from the client"); close (cli->socket); del_client (cli); return; } switch (cmd) { case CMD_QUIT: logit ("Exit request from the client"); close (cli->socket); del_client (cli); server_quit = 1; break; case CMD_LIST_CLEAR: logit ("Clearing the list"); audio_plist_clear (); break; case CMD_LIST_ADD: if (!req_list_add(cli)) err = 1; break; case CMD_PLAY: if (!req_play(cli)) err = 1; break; case CMD_DISCONNECT: logit ("Client disconnected"); close (cli->socket); del_client (cli); break; case CMD_PAUSE: audio_pause (); break; case CMD_UNPAUSE: audio_unpause (); break; case CMD_STOP: audio_stop (); break; case CMD_GET_CTIME: if (!send_data_int(cli, MAX(0, audio_get_time()))) err = 1; break; case CMD_SEEK: if (!req_seek(cli)) err = 1; break; case CMD_JUMP_TO: if (!req_jump_to(cli)) err = 1; break; case CMD_GET_SNAME: if (!send_sname(cli)) err = 1; break; case CMD_GET_STATE: if (!send_data_int(cli, audio_get_state())) err = 1; break; case CMD_GET_BITRATE: if (!send_data_int(cli, sound_info.bitrate)) err = 1; break; case CMD_GET_AVG_BITRATE: if (!send_data_int(cli, sound_info.avg_bitrate)) err = 1; break; case CMD_GET_RATE: if (!send_data_int(cli, sound_info.rate)) err = 1; break; case CMD_GET_CHANNELS: if (!send_data_int(cli, sound_info.channels)) err = 1; break; case CMD_NEXT: audio_next (); break; case CMD_PREV: audio_prev (); break; case CMD_PING: if (!send_int(cli->socket, EV_PONG)) err = 1; break; case CMD_GET_OPTION: if (!send_option(cli)) err = 1; break; case CMD_SET_OPTION: if (!get_set_option(cli)) err = 1; break; case CMD_GET_MIXER: if (!send_data_int(cli, audio_get_mixer())) err = 1; break; case CMD_SET_MIXER: if (!set_mixer(cli)) err = 1; break; case CMD_DELETE: if (!delete_item(cli)) err = 1; break; case CMD_SEND_PLIST_EVENTS: cli->wants_plist_events = 1; logit ("Request for events"); break; case CMD_GET_PLIST: if (!get_client_plist(cli)) err = 1; break; case CMD_SEND_PLIST: if (!req_send_plist(cli)) err = 1; break; case CMD_CAN_SEND_PLIST: cli->can_send_plist = 1; break; case CMD_CLI_PLIST_ADD: case CMD_CLI_PLIST_DEL: case CMD_CLI_PLIST_CLEAR: case CMD_CLI_PLIST_MOVE: if (!plist_sync_cmd(cli, cmd)) err = 1; break; case CMD_LOCK: if (!client_lock(cli)) err = 1; break; case CMD_UNLOCK: if (!client_unlock(cli)) err = 1; break; case CMD_GET_SERIAL: if (!send_serial(cli)) err = 1; break; case CMD_PLIST_GET_SERIAL: if (!req_plist_get_serial(cli)) err = 1; break; case CMD_PLIST_SET_SERIAL: if (!req_plist_set_serial(cli)) err = 1; break; case CMD_GET_TAGS: if (!req_get_tags(cli)) err = 1; break; case CMD_TOGGLE_MIXER_CHANNEL: req_toggle_mixer_channel (); break; case CMD_TOGGLE_SOFTMIXER: req_toggle_softmixer (); break; case CMD_GET_MIXER_CHANNEL_NAME: if (!req_get_mixer_channel_name(cli)) err = 1; break; case CMD_GET_FILE_TAGS: if (!get_file_tags(client_id)) err = 1; break; case CMD_ABORT_TAGS_REQUESTS: if (!abort_tags_requests(client_id)) err = 1; break; case CMD_LIST_MOVE: if (!req_list_move(cli)) err = 1; break; case CMD_TOGGLE_EQUALIZER: req_toggle_equalizer(); break; case CMD_EQUALIZER_REFRESH: req_equalizer_refresh(); break; case CMD_EQUALIZER_PREV: req_equalizer_prev(); break; case CMD_EQUALIZER_NEXT: req_equalizer_next(); break; case CMD_TOGGLE_MAKE_MONO: req_toggle_make_mono(); break; case CMD_QUEUE_ADD: if (!req_queue_add(cli)) err = 1; break; case CMD_QUEUE_DEL: if (!req_queue_del(cli)) err = 1; break; case CMD_QUEUE_CLEAR: logit ("Clearing the queue"); audio_queue_clear (); add_event_all (EV_QUEUE_CLEAR, NULL); break; case CMD_QUEUE_MOVE: if (!req_queue_move(cli)) err = 1; break; case CMD_GET_QUEUE: if (!req_send_queue(cli)) err = 1; break; default: logit ("Bad command (0x%x) from the client", cmd); err = 1; } if (err) { logit ("Closing client connection due to error"); close (cli->socket); del_client (cli); } } /* Add clients file descriptors to fds. */ static void add_clients_fds (fd_set *read, fd_set *write) { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1) { if (locking_client() == -1 || is_locking(&clients[i])) FD_SET (clients[i].socket, read); LOCK (clients[i].events_mtx); if (!event_queue_empty(&clients[i].events)) FD_SET (clients[i].socket, write); UNLOCK (clients[i].events_mtx); } } /* Return the maximum fd from clients and the argument. */ static int max_fd (int max) { int i; if (wake_up_pipe[0] > max) max = wake_up_pipe[0]; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket > max) max = clients[i].socket; return max; } /* Handle clients whose fds are ready to read. */ static void handle_clients (fd_set *fds) { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1 && FD_ISSET(clients[i].socket, fds)) { if (locking_client() == -1 || is_locking(&clients[i])) handle_command (i); else debug ("Not getting a command from client with" " fd %d because of lock", clients[i].socket); } } /* Close all client connections sending EV_EXIT. */ static void close_clients () { int i; for (i = 0; i < CLIENTS_MAX; i++) if (clients[i].socket != -1) { send_int (clients[i].socket, EV_EXIT); close (clients[i].socket); del_client (&clients[i]); } } /* Handle incoming connections */ void server_loop () { struct sockaddr_un client_name; socklen_t name_len = sizeof (client_name); logit ("MOC server started, pid: %d", getpid()); assert (server_sock != -1); log_circular_start (); do { int res; fd_set fds_write, fds_read; FD_ZERO (&fds_read); FD_ZERO (&fds_write); FD_SET (server_sock, &fds_read); FD_SET (wake_up_pipe[0], &fds_read); add_clients_fds (&fds_read, &fds_write); res = 0; if (!server_quit) res = select (max_fd(server_sock)+1, &fds_read, &fds_write, NULL, NULL); if (res == -1 && errno != EINTR && !server_quit) fatal ("select() failed: %s", xstrerror (errno)); if (!server_quit && res >= 0) { if (FD_ISSET(server_sock, &fds_read)) { int client_sock; debug ("accept()ing connection..."); client_sock = accept (server_sock, (struct sockaddr *)&client_name, &name_len); if (client_sock == -1) fatal ("accept() failed: %s", xstrerror (errno)); logit ("Incoming connection"); if (!add_client(client_sock)) busy (client_sock); } if (FD_ISSET(wake_up_pipe[0], &fds_read)) { int w; logit ("Got 'wake up'"); if (read(wake_up_pipe[0], &w, sizeof(w)) < 0) fatal ("Can't read wake up signal: %s", xstrerror (errno)); } send_events (&fds_write); handle_clients (&fds_read); } if (server_quit) logit ("Exiting..."); } while (!server_quit); log_circular_log (); log_circular_stop (); close_clients (); clients_cleanup (); close (server_sock); server_sock = -1; server_shutdown (); } void set_info_bitrate (const int bitrate) { sound_info.bitrate = bitrate; add_event_all (EV_BITRATE, NULL); } void set_info_channels (const int channels) { sound_info.channels = channels; add_event_all (EV_CHANNELS, NULL); } void set_info_rate (const int rate) { sound_info.rate = rate; add_event_all (EV_RATE, NULL); } void set_info_avg_bitrate (const int avg_bitrate) { sound_info.avg_bitrate = avg_bitrate; add_event_all (EV_AVG_BITRATE, NULL); } /* Notify the client about change of the player state. */ void state_change () { add_event_all (EV_STATE, NULL); } void ctime_change () { add_event_all (EV_CTIME, NULL); } void tags_change () { add_event_all (EV_TAGS, NULL); } void status_msg (const char *msg) { add_event_all (EV_STATUS_MSG, msg); } void tags_response (const int client_id, const char *file, const struct file_tags *tags) { assert (file != NULL); assert (tags != NULL); assert (LIMIT(client_id, CLIENTS_MAX)); if (clients[client_id].socket != -1) { struct tag_ev_response *data = (struct tag_ev_response *)xmalloc ( sizeof(struct tag_ev_response)); data->file = xstrdup (file); data->tags = tags_dup (tags); add_event (&clients[client_id], EV_FILE_TAGS, data); wake_up_server (); } } void ev_audio_start () { add_event_all (EV_AUDIO_START, NULL); } void ev_audio_stop () { add_event_all (EV_AUDIO_STOP, NULL); } /* Announce to clients that first file from the queue is being played * and therefore needs to be removed from it */ /* XXX: this function is called from player thread and add_event_all * imho doesn't properly lock all shared variables -- possible * race condition??? */ void server_queue_pop (const char *filename) { debug ("Queue pop -- broadcasting EV_QUEUE_DEL"); add_event_all (EV_QUEUE_DEL, filename); } moc-2.6.0~svn-r3005/server.h0000664000076400000500000000143112747016164014615 0ustar riesebiesrc#ifndef SERVER_H #define SERVER_H #ifdef __cplusplus extern "C" { #endif #include "playlist.h" #define CLIENTS_MAX 10 void server_init (int debug, int foreground); void server_loop (); void server_error (const char *file, int line, const char *function, const char *msg); void state_change (); void set_info_rate (const int rate); void set_info_channels (const int channels); void set_info_bitrate (const int bitrate); void set_info_avg_bitrate (const int avg_bitrate); void tags_change (); void ctime_change (); void status_msg (const char *msg); void tags_response (const int client_id, const char *file, const struct file_tags *tags); void ev_audio_start (); void ev_audio_stop (); void server_queue_pop (const char *filename); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/sndio_out.c0000664000076400000500000000742112424322553015304 0ustar riesebiesrc/* * MOC - music on console * * SNDIO sound driver for MOC by Alexander Polakov. * Copyright (C) 2011 Alexander Polakov * * 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. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_SNDIO_H # include #endif #include #include "common.h" #include "audio.h" #include "log.h" #define PCT_TO_SIO(pct) ((127 * (pct) + 50) / 100) #define SIO_TO_PCT(vol) ((100 * (vol) + 64) / 127) static struct sio_hdl *hdl = NULL; static int curvol = 100; static struct sound_params params = { 0, 0, 0 }; static void sndio_close (); static void volume_cb (void *unused ATTR_UNUSED, unsigned int vol) { curvol = SIO_TO_PCT(vol); } static int sndio_init (struct output_driver_caps *caps) { assert (caps != NULL); caps->formats = SFMT_S8 | SFMT_U8 | SFMT_U16 | SFMT_S16 | SFMT_NE; caps->min_channels = 1; caps->max_channels = 2; return 1; } static void sndio_shutdown () { if (hdl) sndio_close (); } /* Return 0 on failure. */ static int sndio_open (struct sound_params *sound_params) { struct sio_par par; assert (hdl == NULL); if ((hdl = sio_open (NULL, SIO_PLAY, 0)) == NULL) return 0; params = *sound_params; sio_initpar (&par); /* Add volume change callback. */ sio_onvol (hdl, volume_cb, NULL); par.rate = sound_params->rate; par.pchan = sound_params->channels; par.bits = (((sound_params->fmt & SFMT_S8) || (sound_params->fmt & SFMT_U8)) ? 8 : 16); par.le = SIO_LE_NATIVE; par.sig = (((sound_params->fmt & SFMT_S16) || (sound_params->fmt & SFMT_S8)) ? 1 : 0); par.round = par.rate / 8; par.appbufsz = par.round * 2; logit ("rate %d pchan %d bits %d sign %d", par.rate, par.pchan, par.bits, par.sig); if (!sio_setpar (hdl, &par) || !sio_getpar (hdl, &par) || !sio_start (hdl)) { logit ("Failed to set sndio parameters."); sio_close (hdl); hdl = NULL; return 0; } sio_setvol (hdl, PCT_TO_SIO(curvol)); return 1; } /* Return the number of bytes played, or -1 on error. */ static int sndio_play (const char *buff, const size_t size) { int count; assert (hdl != NULL); count = (int) sio_write (hdl, buff, size); if (!count && sio_eof (hdl)) return -1; return count; } static void sndio_close () { assert (hdl != NULL); sio_stop (hdl); sio_close (hdl); hdl = NULL; } static int sndio_read_mixer () { return curvol; } static void sndio_set_mixer (int vol) { if (hdl != NULL) sio_setvol (hdl, PCT_TO_SIO (vol)); } static int sndio_get_buff_fill () { assert (hdl != NULL); /* Since we cannot stop SNDIO playing the samples already in * its buffer, there will never be anything left unheard. */ return 0; } static int sndio_reset () { assert (hdl != NULL); /* SNDIO will continue to play the samples already in its buffer * regardless of what we do, so there's nothing we can do. */ return 1; } static int sndio_get_rate () { assert (hdl != NULL); return params.rate; } static void sndio_toggle_mixer_channel () { assert (hdl != NULL); } static char *sndio_get_mixer_channel_name () { return xstrdup ("moc"); } void sndio_funcs (struct hw_funcs *funcs) { funcs->init = sndio_init; funcs->shutdown = sndio_shutdown; funcs->open = sndio_open; funcs->close = sndio_close; funcs->play = sndio_play; funcs->read_mixer = sndio_read_mixer; funcs->set_mixer = sndio_set_mixer; funcs->get_buff_fill = sndio_get_buff_fill; funcs->reset = sndio_reset; funcs->get_rate = sndio_get_rate; funcs->toggle_mixer_channel = sndio_toggle_mixer_channel; funcs->get_mixer_channel_name = sndio_get_mixer_channel_name; } moc-2.6.0~svn-r3005/sndio_out.h0000664000076400000500000000026513334465526015321 0ustar riesebiesrc#ifndef SNDIO_OUT_H #define SNDIO_OUT_H #include "audio.h" #ifdef __cplusplus extern "C" { #endif void sndio_funcs (struct hw_funcs *funcs); #ifdef __cplusplus } #endif #endif moc-2.6.0~svn-r3005/softmixer.c0000664000076400000500000003175512503672623015334 0ustar riesebiesrc/* * MOC - music on console * Copyright (C) 2004-2008 Damian Pietras * * Softmixer-extension Copyright (C) 2007-2008 Hendrik Iben * Provides a software-mixer to regulate volume independent from * hardware. * * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include /* #define DEBUG */ #include "common.h" #include "audio.h" #include "audio_conversion.h" #include "softmixer.h" #include "options.h" #include "files.h" #include "log.h" static int active; static int mix_mono; static int mixer_val, mixer_amp, mixer_real; static float mixer_realf; static void softmixer_read_config(); static void softmixer_write_config(); /* public code */ char *softmixer_name() { return xstrdup((active)?SOFTMIXER_NAME:SOFTMIXER_NAME_OFF); } void softmixer_init() { active = 0; mix_mono = 0; mixer_amp = 100; softmixer_set_value(100); softmixer_read_config(); logit ("Softmixer initialized"); } void softmixer_shutdown() { if(options_get_bool(SOFTMIXER_SAVE_OPTION)) softmixer_write_config(); logit ("Softmixer stopped"); } void softmixer_set_value(const int val) { mixer_val = CLAMP(0, val, 100); mixer_real = (mixer_val * mixer_amp) / 100; mixer_real = CLAMP(SOFTMIXER_MIN, mixer_real, SOFTMIXER_MAX); mixer_realf = ((float)mixer_real)/100.0f; } int softmixer_get_value() { return mixer_val; } void softmixer_set_active(int act) { if(act) active = 1; else active = 0; } int softmixer_is_active() { return active; } void softmixer_set_mono(int mono) { if(mono) mix_mono = 1; else mix_mono = 0; } int softmixer_is_mono() { return mix_mono; } /* private code */ static void process_buffer_u8(uint8_t *buf, size_t samples); static void process_buffer_s8(int8_t *buf, size_t samples); static void process_buffer_u16(uint16_t *buf, size_t samples); static void process_buffer_s16(int16_t *buf, size_t samples); static void process_buffer_u32(uint32_t *buf, size_t samples); static void process_buffer_s32(int32_t *buf, size_t samples); static void process_buffer_float(float *buf, size_t samples); static void mix_mono_u8(uint8_t *buf, int channels, size_t samples); static void mix_mono_s8(int8_t *buf, int channels, size_t samples); static void mix_mono_u16(uint16_t *buf, int channels, size_t samples); static void mix_mono_s16(int16_t *buf, int channels, size_t samples); static void mix_mono_u32(uint32_t *buf, int channels, size_t samples); static void mix_mono_s32(int32_t *buf, int channels, size_t samples); static void mix_mono_float(float *buf, int channels, size_t samples); static void softmixer_read_config() { char *cfname = create_file_name(SOFTMIXER_SAVE_FILE); FILE *cf = fopen(cfname, "r"); if(cf==NULL) { logit ("Unable to read softmixer configuration"); return; } char *linebuffer=NULL; int tmp; while((linebuffer=read_line(cf))) { if( strncasecmp ( linebuffer , SOFTMIXER_CFG_ACTIVE , strlen(SOFTMIXER_CFG_ACTIVE) ) == 0 ) { if(sscanf(linebuffer, "%*s %i", &tmp)>0) { if(tmp>0) { active = 1; } else { active = 0; } } } if( strncasecmp ( linebuffer , SOFTMIXER_CFG_AMP , strlen(SOFTMIXER_CFG_AMP) ) == 0 ) { if(sscanf(linebuffer, "%*s %i", &tmp)>0) { if(RANGE(SOFTMIXER_MIN, tmp, SOFTMIXER_MAX)) { mixer_amp = tmp; } else { logit ("Tried to set softmixer amplification out of range."); } } } if( strncasecmp ( linebuffer , SOFTMIXER_CFG_VALUE , strlen(SOFTMIXER_CFG_VALUE) ) == 0 ) { if(sscanf(linebuffer, "%*s %i", &tmp)>0) { if(RANGE(0, tmp, 100)) { softmixer_set_value(tmp); } else { logit ("Tried to set softmixer value out of range."); } } } if( strncasecmp ( linebuffer , SOFTMIXER_CFG_MONO , strlen(SOFTMIXER_CFG_MONO) ) == 0 ) { if(sscanf(linebuffer, "%*s %i", &tmp)>0) { if(tmp>0) { mix_mono = 1; } else { mix_mono = 0; } } } free(linebuffer); } fclose(cf); } static void softmixer_write_config() { char *cfname = create_file_name(SOFTMIXER_SAVE_FILE); FILE *cf = fopen(cfname, "w"); if(cf==NULL) { logit ("Unable to write softmixer configuration"); return; } fprintf(cf, "%s %i\n", SOFTMIXER_CFG_ACTIVE, active); fprintf(cf, "%s %i\n", SOFTMIXER_CFG_AMP, mixer_amp); fprintf(cf, "%s %i\n", SOFTMIXER_CFG_VALUE, mixer_val); fprintf(cf, "%s %i\n", SOFTMIXER_CFG_MONO, mix_mono); fclose(cf); logit ("Softmixer configuration written"); } void softmixer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params) { int do_softmix, do_monomix; debug ("Processing %zu bytes...", size); do_softmix = active && (mixer_real != 100); do_monomix = mix_mono && (sound_params->channels > 1); if(!do_softmix && !do_monomix) return; long sound_endianness = sound_params->fmt & SFMT_MASK_ENDIANNESS; long sound_format = sound_params->fmt & SFMT_MASK_FORMAT; int samplewidth = sfmt_Bps(sound_format); int is_float = (sound_params->fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT; int need_endianness_swap = 0; if((sound_endianness != SFMT_NE) && (samplewidth > 1) && (!is_float)) { need_endianness_swap = 1; } assert (size % (samplewidth * sound_params->channels) == 0); /* setup samples to perform arithmetic */ if(need_endianness_swap) { debug ("Converting endianness before mixing"); if(samplewidth == 4) audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t)); else audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t)); } switch(sound_format) { case SFMT_U8: if(do_softmix) process_buffer_u8((uint8_t *)buf, size); if(do_monomix) mix_mono_u8((uint8_t *)buf, sound_params->channels, size); break; case SFMT_S8: if(do_softmix) process_buffer_s8((int8_t *)buf, size); if(do_monomix) mix_mono_s8((int8_t *)buf, sound_params->channels, size); break; case SFMT_U16: if(do_softmix) process_buffer_u16((uint16_t *)buf, size / sizeof(uint16_t)); if(do_monomix) mix_mono_u16((uint16_t *)buf, sound_params->channels, size / sizeof(uint16_t)); break; case SFMT_S16: if(do_softmix) process_buffer_s16((int16_t *)buf, size / sizeof(int16_t)); if(do_monomix) mix_mono_s16((int16_t *)buf, sound_params->channels, size / sizeof(int16_t)); break; case SFMT_U32: if(do_softmix) process_buffer_u32((uint32_t *)buf, size / sizeof(uint32_t)); if(do_monomix) mix_mono_u32((uint32_t *)buf, sound_params->channels, size / sizeof(uint32_t)); break; case SFMT_S32: if(do_softmix) process_buffer_s32((int32_t *)buf, size / sizeof(int32_t)); if(do_monomix) mix_mono_s32((int32_t *)buf, sound_params->channels, size / sizeof(int32_t)); break; case SFMT_FLOAT: if(do_softmix) process_buffer_float((float *)buf, size / sizeof(float)); if(do_monomix) mix_mono_float((float *)buf, sound_params->channels, size / sizeof(float)); break; } /* restore sample-endianness */ if(need_endianness_swap) { debug ("Restoring endianness after mixing"); if(samplewidth == 4) audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t)); else audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t)); } } static void process_buffer_u8(uint8_t *buf, size_t samples) { size_t i; debug ("mixing"); for(i=0; i>1); tmp *= mixer_real; tmp /= 100; tmp += (UINT8_MAX>>1); tmp = CLAMP(0, tmp, UINT8_MAX); buf[i] = (uint8_t)tmp; } } static void process_buffer_s8(int8_t *buf, size_t samples) { size_t i; debug ("mixing"); for(i=0; i>1); tmp *= mixer_real; tmp /= 100; tmp += (UINT16_MAX>>1); tmp = CLAMP(0, tmp, UINT16_MAX); buf[i] = (uint16_t)tmp; } } static void process_buffer_s16(int16_t *buf, size_t samples) { size_t i; debug ("mixing"); for(i=0; i>1); tmp *= mixer_real; tmp /= 100; tmp += (UINT32_MAX>>1); tmp = CLAMP(0, tmp, UINT32_MAX); buf[i] = (uint32_t)tmp; } } static void process_buffer_s32(int32_t *buf, size_t samples) { size_t i; debug ("mixing"); for(i=0; i 1); while(i < samples) { int16_t mono = 0; for(c=0; c 1); while(i < samples) { int16_t mono = 0; for(c=0; c 1); while(i < samples) { int32_t mono = 0; for(c=0; c 1); while(i < samples) { int32_t mono = 0; for(c=0; c 1); while(i < samples) { int64_t mono = 0; for(c=0; c 1); while(i < samples) { int64_t mono = 0; for(c=0; c 1); while(i < samples) { float mono = 0.0f; for(c=0; c